Skip to content

feat(android): add kotlin-pingonemfa sample app - #129

Open
EvgeniyMish wants to merge 3 commits into
ForgeRock:mainfrom
EvgeniyMish:feature/kotlin-pingonemfa-sample
Open

feat(android): add kotlin-pingonemfa sample app#129
EvgeniyMish wants to merge 3 commits into
ForgeRock:mainfrom
EvgeniyMish:feature/kotlin-pingonemfa-sample

Conversation

@EvgeniyMish

@EvgeniyMish EvgeniyMish commented Aug 5, 2026

Copy link
Copy Markdown

Prerequisites:
Until the sdk isn't released to MavenCentral deploy the PingOne MFA SDK to your local Maven repository:

./gradlew publishToMavenLocal

New Android Kotlin/Compose sample demonstrating PingOne MFA SDK integration: QR-code account pairing, TOTP display with live countdown, and push-notification approve/deny flows (default, number-challenge, dry-run, and server-cancel).

Key implementation details:

  • PingOneSampleApplication performs one-time SDK init and FCM token registration in a process-lifetime CoroutineScope, sequentially (token registration runs only after initialize() succeeds) so the device is always registered for push on first install.

  • PushNotificationService forks foreground vs. background on arrival: foreground launches PushNotificationActivity directly via startActivity (BAL-safe because ProcessLifecycleOwner confirms foreground); background posts a high-priority notification with setFullScreenIntent so the OS wakes the screen from lock without the service calling startActivity.

  • PushNotification is carried as a Parcelable Intent extra so PushNotificationActivity survives process death between banner post and user tap. PushNotificationStore is narrowed to in-process cancel-path coordination only.

  • NotificationCancelBus uses MutableSharedFlow(replay=1) so a cancel signal emitted before PushNotificationActivity subscribes is not dropped.

  • PushNotificationViewModel owns approve/deny in viewModelScope so in-flight SDK calls survive Activity recreation. onNewIntent swaps the displayed notification via Compose state (no recreate()) and calls resetState() to clear stale dialog results atomically.

  • OTP countdown uses a SystemClock.elapsedRealtime() deadline anchored at SDK response time and stored in ViewModel, so the counter resumes at the true remaining time after navigation rather than resetting to 30 s. otpVersion counter guarantees LaunchedEffect restarts on every generateOtp() outcome including failure.

  • QrCodeAnalyzer is a single remembered instance with DisposableEffect cleanup; scanned guard uses AtomicBoolean for thread-safe compare-and-set between camera executor and composition threads. CameraProvider binding uses addListener(ContextCompat.getMainExecutor) to avoid blocking the main thread and satisfy CameraX @mainthread assertions.

  • All strings externalised to strings.xml; MIT copyright headers on every source file; DiagnosticLogger StateFlow updates use update() for atomicity.

Summary by CodeRabbit

  • New Features

    • Added a Kotlin Android sample app for PingOne MFA.
    • Supports account pairing through QR scanning or manual entry.
    • Displays rotating OTP codes with expiration countdowns.
    • Supports push authentication approval, denial, and number challenges.
    • Provides actionable push notifications with approve and deny options.
    • Includes diagnostic log viewing, clearing, and sharing.
    • Added light and dark themes, navigation, and branded app icons.
  • Documentation

    • Added setup, requirements, architecture, supported features, and notification-flow documentation.

New Android Kotlin/Compose sample demonstrating PingOne MFA SDK
integration: QR-code account pairing, TOTP display with live countdown,
and push-notification approve/deny flows (default, number-challenge,
dry-run, and server-cancel).

Key implementation details:

- PingOneSampleApplication performs one-time SDK init and FCM token
  registration in a process-lifetime CoroutineScope, sequentially
  (token registration runs only after initialize() succeeds) so the
  device is always registered for push on first install.

- PushNotificationService forks foreground vs. background on arrival:
  foreground launches PushNotificationActivity directly via startActivity
  (BAL-safe because ProcessLifecycleOwner confirms foreground); background
  posts a high-priority notification with setFullScreenIntent so the OS
  wakes the screen from lock without the service calling startActivity.

- PushNotification is carried as a Parcelable Intent extra so
  PushNotificationActivity survives process death between banner post
  and user tap. PushNotificationStore is narrowed to in-process
  cancel-path coordination only.

- NotificationCancelBus uses MutableSharedFlow(replay=1) so a cancel
  signal emitted before PushNotificationActivity subscribes is not
  dropped.

- PushNotificationViewModel owns approve/deny in viewModelScope so
  in-flight SDK calls survive Activity recreation. onNewIntent swaps
  the displayed notification via Compose state (no recreate()) and
  calls resetState() to clear stale dialog results atomically.

- OTP countdown uses a SystemClock.elapsedRealtime() deadline anchored
  at SDK response time and stored in ViewModel, so the counter resumes
  at the true remaining time after navigation rather than resetting to
  30 s. otpVersion counter guarantees LaunchedEffect restarts on every
  generateOtp() outcome including failure.

- QrCodeAnalyzer is a single remembered instance with DisposableEffect
  cleanup; scanned guard uses AtomicBoolean for thread-safe
  compare-and-set between camera executor and composition threads.
  CameraProvider binding uses addListener(ContextCompat.getMainExecutor)
  to avoid blocking the main thread and satisfy CameraX @mainthread
  assertions.

- All strings externalised to strings.xml; MIT copyright headers on
  every source file; DiagnosticLogger StateFlow updates use update()
  for atomicity.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@EvgeniyMish, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 17 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b4704146-dba2-41af-aaf2-a3d13e9280e1

📥 Commits

Reviewing files that changed from the base of the PR and between a8c9774 and db9deeb.

📒 Files selected for processing (1)
  • android/kotlin-pingonemfa/.gitignore
📝 Walkthrough

Walkthrough

Added a Kotlin Android PingOne MFA sample. It now initializes the SDK, loads accounts, generates OTPs, supports QR and manual pairing, handles push approvals and denials, and shows diagnostic logs in Compose screens.

Changes

PingOne MFA Android sample

Layer / File(s) Summary
Project foundation and Android resources
android/kotlin-pingonemfa/*.gradle.kts, android/kotlin-pingonemfa/gradle/*, android/kotlin-pingonemfa/gradlew*, android/kotlin-pingonemfa/app/build.gradle.kts, android/kotlin-pingonemfa/app/src/main/AndroidManifest.xml, android/kotlin-pingonemfa/app/src/main/res/*, android/kotlin-pingonemfa/README.md
The project adds Gradle configuration, Android components, resource strings, themes, icons, launcher assets, ignore files, ProGuard rules, Firebase config, and setup documentation.
Application bootstrap and theme
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/{MainActivity.kt,AuthApp.kt,PingOneSampleApplication.kt}, .../data/PingOneViewModel.kt, .../theme/*
The app entry points initialize PingOne MFA, request notification permission, host navigation, and apply Material 3 colors, shapes, and typography. The view model manages accounts, pairing, OTP state, errors, and messages.
Diagnostic logging
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/DiagnosticLogger.kt, .../ui/DiagnosticLogsScreen.kt
The app records bounded diagnostic logs in memory and renders a screen for viewing, exporting, clearing, and sharing them.
Account pairing and OTP UI
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt, .../ui/QrScannerScreen.kt, .../ui/components/*, .../util/QrCodeAnalyzer.kt
The UI displays accounts and OTP countdowns, supports QR and manual pairing, handles camera permissions, and provides reusable controls for accounts, pairing, scanner, and number challenges.
Push notification authentication
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/*, .../service/PushNotificationService.kt, .../ui/PushNotificationScreen.kt
FCM messages route to foreground or background notification flows. The app stores active notifications, handles cancellation, supports approve and deny actions, and displays push challenge states.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • ForgeRock/sdk-sample-apps#112: Both PRs add Kotlin Android Ping Identity authenticator sample apps with overlapping MFA, QR, push notification, Compose UI, Gradle, and diagnostic logging components.

Poem

I hop through code with a happy drum,
QR dots and OTP ticks now come.
Push bells ring, then softly fade,
Logs stay neat in the grove I made.
Deny or approve, the path is clear,
A bunny-built app is here.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the addition of the Kotlin PingOne MFA Android sample app.
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.
✨ 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: 13

🧹 Nitpick comments (9)
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/OtpBox.kt (1)

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

Fix the KDoc reference to the icon.

[Tag] does not resolve to an imported type. The import is androidx.compose.material.icons.filled.Tag, which is a property on Icons.Filled. Write the reference as plain text or as Icons.Default.Tag.

🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/OtpBox.kt`
at line 42, Update the KDoc description in OtpBox to replace the unresolved
[Tag] reference with plain text or a valid Icons.Default.Tag reference, matching
the imported Compose icon API.
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt (1)

60-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The bracket stroke is clipped at the canvas edge.

Canvas clips its content to its own bounds. The corner brackets are drawn on the exact boundary at Offset(0f, 0f) and Offset(w, h). A stroke is centered on its path, so half of the 6.dp width, 3.dp, falls outside the bounds and is cut off. The brackets then appear thinner on the outer side than intended.

Inset the drawing by half of the stroke width.

♻️ Proposed refactor
             val stroke = 6.dp.toPx()
             val arm = 32.dp.toPx()
             val radius = 16.dp.toPx()
-            val w = size.width
-            val h = size.height
+            val inset = stroke / 2f
+            val w = size.width - inset
+            val h = size.height - inset
 
             val corners = listOf(
-                Offset(0f, 0f) to (1f to 1f),    // top-left
+                Offset(inset, inset) to (1f to 1f),    // top-left
                 Offset(w, 0f) to (-1f to 1f),    // top-right
                 Offset(w, h) to (-1f to -1f),    // bottom-right
-                Offset(0f, h) to (1f to -1f),    // bottom-left
+                Offset(inset, h) to (1f to -1f),    // bottom-left
             )

Also change the y of the top-right pivot and the x of the bottom-right pivot to use inset where they sit on the top or left edge.

🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt`
around lines 60 - 93, Update the Canvas bracket drawing in the corners loop to
inset every pivot by half the stroke width, keeping the full centered stroke
inside the canvas. Apply the inset to both coordinates where a corner lies on an
edge, including the top-right y coordinate and bottom-right x coordinate, while
preserving the existing bracket directions and lengths.
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/LoadingIndicator.kt (1)

44-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not hardcode the text color in a reusable component.

Color.White is readable on the gray camera background in QrScannerScreen, but it becomes invisible on a light surface. LoadingIndicator is a general-purpose component. Expose the color as a parameter that defaults to a theme color.

♻️ Proposed refactor
 fun LoadingIndicator(
     message: String,
-    modifier: Modifier = Modifier
+    modifier: Modifier = Modifier,
+    contentColor: Color = MaterialTheme.colorScheme.onSurface,
 ) {
@@
-        CircularProgressIndicator()
+        CircularProgressIndicator(color = contentColor)
         Spacer(modifier = Modifier.height(16.dp))
         Text(
             text = message,
             style = MaterialTheme.typography.bodyLarge,
-            color = Color.White
+            color = contentColor
         )

Pass contentColor = Color.White at the QrScannerScreen call site to keep the current appearance.

🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/LoadingIndicator.kt`
around lines 44 - 50, Update the reusable LoadingIndicator component to expose a
content-color parameter with a theme-color default, and use it for the message
Text instead of hardcoded Color.White. At the QrScannerScreen call site,
explicitly pass Color.White to preserve its existing appearance.
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt (1)

138-140: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the account guard.

firstOrNull()?.let { ... } discards the account and only tests for a non-empty list. Use an explicit emptiness check to state the intent.

♻️ Proposed refactor
-        if (isActive && !uiState.isRefreshingOtp) {
-            uiState.accounts.firstOrNull()?.let { viewModel.generateOtp() }
-        }
+        if (isActive && !uiState.isRefreshingOtp && uiState.accounts.isNotEmpty()) {
+            viewModel.generateOtp()
+        }
🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt`
around lines 138 - 140, Update the account guard in AccountsScreen so the
generateOtp call is gated by an explicit non-empty check on uiState.accounts
instead of using firstOrNull()?.let, since the account value is unused. Keep the
existing isActive and !uiState.isRefreshingOtp conditions unchanged, and
preserve the same behavior of only calling viewModel.generateOtp() when at least
one account exists.
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualPairingPanel.kt (1)

40-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Apply the Compose API conventions.

Three small items in this signature and body:

  1. Line 42: modifier precedes the required parameters. The Compose API guidelines place modifier as the first optional parameter, after all required parameters.
  2. Lines 66-67: the code uses the fully qualified androidx.compose.material3.MaterialTheme. Import the symbol instead.
  3. Line 76: 12 is a magic number. Extract it to a named constant.
♻️ Proposed refactor
+import androidx.compose.material3.MaterialTheme
+
+private const val MIN_PAIRING_KEY_LENGTH = 12
+
 `@Composable`
 fun ManualPairingPanel(
-    modifier: Modifier = Modifier,
     value: String,
     onValueChange: (String) -> Unit,
     onPair: (String) -> Unit,
+    modifier: Modifier = Modifier,
     isPairing: Boolean = false,
 ) {
@@
                 colors = OutlinedTextFieldDefaults.colors(
-                    unfocusedContainerColor = androidx.compose.material3.MaterialTheme.colorScheme.surface,
-                    focusedContainerColor = androidx.compose.material3.MaterialTheme.colorScheme.surface,
+                    unfocusedContainerColor = MaterialTheme.colorScheme.surface,
+                    focusedContainerColor = MaterialTheme.colorScheme.surface,
                 ),
@@
-                enabled = value.trim().length >= 12 && !isPairing,
+                enabled = value.trim().length >= MIN_PAIRING_KEY_LENGTH && !isPairing,

All call sites use named arguments, so the parameter reorder is source compatible.

🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualPairingPanel.kt`
around lines 40 - 76, Update ManualPairingPanel so required parameters precede
the optional modifier parameter, then import MaterialTheme and use it directly
for both container color references. Extract the pairing-length threshold 12
into a descriptive named constant and use that constant in the enabled
condition.
android/kotlin-pingonemfa/app/src/main/res/drawable/ping_logo.xml (1)

8-27: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce the logo vector cost and confirm the unused animation group.

Three points on this asset:

  1. Intrinsic size is 794dp square. That size only applies when a caller sets no size modifier, and it produces a very large default. Set android:width/android:height to the intended display size (for example 120dp) and keep the 794 viewport.
  2. The second path begins with the full-canvas rectangle M1,396.5L1,793 397,793L793,793 793,396.5L793,0 397,0L1,0 1,396.5 and then repeats the same glyph geometry as the first path. If the second path paints over the first, the first path is dead weight and doubles the rasterization cost.
  3. The group is named animationGroup and declares pivotX/pivotY, but no AnimatedVectorDrawable in this cohort targets it. If no animator exists, remove the group.

Run the following script to check for an animator that targets animationGroup and for the display sizes used by callers:

#!/bin/bash
# Description: Find animators targeting `animationGroup` and all usages of `ping_logo`.
set -euo pipefail

echo "=== animated-vector / objectAnimator resources ==="
fd -e xml . android/kotlin-pingonemfa/app/src/main/res --exec rg -l 'animated-vector|objectAnimator' {} \; || true

echo "=== references to animationGroup ==="
rg -n 'animationGroup' android/kotlin-pingonemfa || true

echo "=== usages of ping_logo and nearby size modifiers ==="
rg -n -C 4 'ping_logo' android/kotlin-pingonemfa || true
🤖 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 `@android/kotlin-pingonemfa/app/src/main/res/drawable/ping_logo.xml` around
lines 8 - 27, Reduce ping_logo’s intrinsic width and height to the intended
caller display size while preserving the 794 viewport, then inspect usages and
animator resources for animationGroup. If no animator targets animationGroup,
remove the group wrapper and its pivot/name attributes; also remove the
redundant second path when its full-canvas paint and duplicated glyph geometry
make the first path unused, preserving the final rendered logo.
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml (1)

6-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Three copied Material icons miss the project copyright header, and two pin a fixed white tint. The three assets were added from the Material icon set without the MIT header that the other new resources in this PR carry. Two of them also keep android:tint="#FFFFFF", which blocks theming at the call site.

  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml#L6-L9: add the MIT copyright header and remove android:tint="#FFFFFF"; tint the icon where it is drawn.
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_close.xml#L6-L9: add the MIT copyright header and remove android:tint="#FFFFFF"; tint the icon where it is drawn.
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_notification.xml#L1-L9: add the MIT copyright header. Keep the white fill, because Android renders the small notification icon as an alpha silhouette.
🤖 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 `@android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml` around
lines 6 - 9, Add the missing MIT copyright header to the three copied Material
icon drawables at
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml#L6-L9,
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_close.xml#L6-L9, and
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_notification.xml#L1-L9;
also remove the fixed android:tint="`#FFFFFF`" from ic_check.xml and ic_close.xml
so those icons can be tinted at the call site, while leaving the white fill
behavior in ic_notification.xml unchanged.
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationHelper.kt (1)

105-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the dropped notification when notifications are disabled.

If areNotificationsEnabled() returns false, this method returns without posting and without recording anything. The background push is then lost with no trace, and PushNotificationStore stays empty, so a later cancel push also finds nothing. Add a diagnostic log for this branch so the sample surfaces the cause.

♻️ Proposed change
 with(NotificationManagerCompat.from(context)) {
     if (areNotificationsEnabled()) {
         // Record in-process only once we know the banner will actually be posted.
         PushNotificationStore.put(notification)
         notify(notificationId, builder.build())
+    } else {
+        DiagnosticLogger.w(
+            "Notifications are disabled — dropped push ${notification.id}",
+            null
+        )
     }
 }

Add the import:

import com.pingidentity.samples.pingonesample.data.DiagnosticLogger
🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationHelper.kt`
around lines 105 - 111, Add DiagnosticLogger to the notification helper and log
a diagnostic message in the false branch of areNotificationsEnabled(), including
enough context to identify the dropped notification. Keep the existing
PushNotificationStore.put and notify behavior unchanged when notifications are
enabled.
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationViewModel.kt (1)

42-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared approve/deny execution flow.

approve and deny repeat the same structure: the loading guard, the isLoading update, the onSuccess / onFailure handling, and the catch block. Only the SDK call and the three strings differ. Extract a private helper that takes the success title, the success message, the failure fallback, and a suspending action. This removes about 40 duplicated lines and keeps the two paths consistent when one changes.

♻️ Sketch of the extraction
private fun run(
    successTitle: String,
    successMessage: String,
    failureFallback: String,
    action: suspend () -> Result<Unit>,
) {
    if (_uiState.value.isLoading) return
    viewModelScope.launch {
        _uiState.update { it.copy(isLoading = true) }
        val dialogState = try {
            action().fold(
                onSuccess = { PushDialogState.Success(successTitle, successMessage) },
                onFailure = { e ->
                    DiagnosticLogger.e("PushNotificationViewModel: failed — ${e.message}", e)
                    PushDialogState.Error(e.message ?: failureFallback)
                },
            )
        } catch (e: Exception) {
            DiagnosticLogger.e("PushNotificationViewModel: threw — ${e.message}", e)
            PushDialogState.Error(e.message ?: failureFallback)
        }
        _uiState.update { it.copy(isLoading = false, dialogState = dialogState) }
    }
}
🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationViewModel.kt`
around lines 42 - 144, Extract the duplicated execution logic from approve and
deny into a private helper in PushNotificationViewModel that accepts the success
title, success message, failure fallback, and a suspending Result<Unit> action.
Move the loading guard, viewModelScope launch, loading-state updates,
success/failure handling, and exception handling into this helper, then have
approve and deny provide only their strings and SDK actions while preserving
existing logging and dialog behavior.
🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/DiagnosticLogger.kt`:
- Around line 43-44: Update DiagnosticLogger’s shared dateFormat usage so every
dateFormat.format call in addLogEntry and exportLogs is protected by the same
lock, or replace dateFormat with a thread-safe formatter while preserving the
existing timestamp format.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/PingOneViewModel.kt`:
- Around line 124-132: Update the generateOtp failure handler in
PingOneViewModel so it clears both generatedCode and otpExpiresAtElapsedMs when
refreshing fails, preventing an expired OTP from remaining in UI state. Preserve
the existing error, isRefreshingOtp, and otpVersion updates; only set a new
explicit retry deadline if the surrounding retry behavior requires automatic
retry.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationActionReceiver.kt`:
- Around line 57-61: Update NotificationActionReceiver.onReceive so
PushNotificationStore.remove() and banner cancellation occur only for recognized
ACTION_APPROVE and ACTION_DENY actions. Move both side effects into those
branches, reusing a dismiss helper if appropriate, and leave unrecognized
actions untouched so the pending request remains answerable.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/service/PushNotificationService.kt`:
- Around line 128-131: Update PushNotificationService.onDestroy to cancel the
coroutine scope before or after calling super.onDestroy, using the
kotlinx.coroutines.cancel extension, so all coroutines launched from
onMessageReceived, onNewToken, and handleNotification are stopped when the
service is destroyed.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt`:
- Around line 122-141: Update the countdown logic in the LaunchedEffect keyed by
uiState.otpVersion so a null otpExpiresAtElapsedMs creates one fixed deadline
using DEFAULT_OTP_TTL_SECONDS from the effect start time. Make
computeSecsRemaining() derive remaining time from that captured deadline on
every tick, allowing secsRemaining to reach zero and the existing generateOtp()
retry to execute.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/AccountAvatar.kt`:
- Around line 77-80: Update generateAvatarColor so hue uses Math.floorMod on
seed.hashCode() with 360, guaranteeing a non-negative value even for
Int.MIN_VALUE; remove the unused kotlin.math.absoluteValue import.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualNumberChallenge.kt`:
- Around line 57-77: Update the input handling in the OutlinedTextField to
accept digits only up to 10 characters, preventing values that
input.toIntOrNull() cannot convert to an Int. Preserve the existing button
behavior for valid non-empty input.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt`:
- Around line 52-57: Update the title Text in QrGrid to use a distinct
camera-overlay instruction instead of R.string.qr_scanner_title, while leaving
QrScannerScreen's BackNavigationTopAppBar label unchanged. Add or reuse an
appropriate localized string such as “Point the camera at the QR code.”

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/QrScannerScreen.kt`:
- Around line 93-116: Update the permission state handling in QrScannerScreen
around hasCameraPermission and LaunchedEffect so the current CAMERA permission
is re-checked on every Lifecycle.Event.ON_RESUME, including changes made in
system settings. Use the lifecycle resume callback to refresh
hasCameraPermission and preserve the existing request flow for denied
permissions.
- Around line 302-309: Fix the teardown ordering so the camera stops delivering
frames before the ML Kit scanner is released: in QrScannerScreen’s
DisposableEffect onDispose, unbind all CameraX use cases with
ProcessCameraProvider.getInstance(context).get().unbindAll() wrapped in
runCatching before calling qrAnalyzer.close() and cameraExecutor.shutdown();
then in QrCodeAnalyzer, add a closed flag that is set in close() before
scanner.close(), and make analyze() exit early after closing the ImageProxy when
that flag is already set so no task can reach scanner.process() after disposal.
- Around line 225-227: Update the exception handling in QrScannerScreen’s camera
error path to provide a non-null fallback when formatting e.message, preventing
the user-facing error from containing the literal “null”. Preserve the existing
cameraErrorTemplate and viewModel.setError flow.

In `@android/kotlin-pingonemfa/gradle/libs.versions.toml`:
- Line 3: Update the kotlin version in the version catalog to a Kotlin release
supported by the project’s declared AGP 9.1.1 and Gradle 9.3.1 versions, and
ensure the org.jetbrains.kotlin.plugin.compose version follows the same
supported Kotlin release. Keep both Kotlin and Compose compiler versions aligned
with the selected AGP.

In `@android/kotlin-pingonemfa/README.md`:
- Around line 14-20: Update the “Getting Started” steps in the README to include
running ./gradlew publishToMavenLocal from the SDK repository root before
opening or building the sample app, then preserve the existing configuration and
build steps.

---

Nitpick comments:
In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationHelper.kt`:
- Around line 105-111: Add DiagnosticLogger to the notification helper and log a
diagnostic message in the false branch of areNotificationsEnabled(), including
enough context to identify the dropped notification. Keep the existing
PushNotificationStore.put and notify behavior unchanged when notifications are
enabled.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationViewModel.kt`:
- Around line 42-144: Extract the duplicated execution logic from approve and
deny into a private helper in PushNotificationViewModel that accepts the success
title, success message, failure fallback, and a suspending Result<Unit> action.
Move the loading guard, viewModelScope launch, loading-state updates,
success/failure handling, and exception handling into this helper, then have
approve and deny provide only their strings and SDK actions while preserving
existing logging and dialog behavior.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt`:
- Around line 138-140: Update the account guard in AccountsScreen so the
generateOtp call is gated by an explicit non-empty check on uiState.accounts
instead of using firstOrNull()?.let, since the account value is unused. Keep the
existing isActive and !uiState.isRefreshingOtp conditions unchanged, and
preserve the same behavior of only calling viewModel.generateOtp() when at least
one account exists.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/LoadingIndicator.kt`:
- Around line 44-50: Update the reusable LoadingIndicator component to expose a
content-color parameter with a theme-color default, and use it for the message
Text instead of hardcoded Color.White. At the QrScannerScreen call site,
explicitly pass Color.White to preserve its existing appearance.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualPairingPanel.kt`:
- Around line 40-76: Update ManualPairingPanel so required parameters precede
the optional modifier parameter, then import MaterialTheme and use it directly
for both container color references. Extract the pairing-length threshold 12
into a descriptive named constant and use that constant in the enabled
condition.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/OtpBox.kt`:
- Line 42: Update the KDoc description in OtpBox to replace the unresolved [Tag]
reference with plain text or a valid Icons.Default.Tag reference, matching the
imported Compose icon API.

In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt`:
- Around line 60-93: Update the Canvas bracket drawing in the corners loop to
inset every pivot by half the stroke width, keeping the full centered stroke
inside the canvas. Apply the inset to both coordinates where a corner lies on an
edge, including the top-right y coordinate and bottom-right x coordinate, while
preserving the existing bracket directions and lengths.

In `@android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml`:
- Around line 6-9: Add the missing MIT copyright header to the three copied
Material icon drawables at
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml#L6-L9,
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_close.xml#L6-L9, and
android/kotlin-pingonemfa/app/src/main/res/drawable/ic_notification.xml#L1-L9;
also remove the fixed android:tint="`#FFFFFF`" from ic_check.xml and ic_close.xml
so those icons can be tinted at the call site, while leaving the white fill
behavior in ic_notification.xml unchanged.

In `@android/kotlin-pingonemfa/app/src/main/res/drawable/ping_logo.xml`:
- Around line 8-27: Reduce ping_logo’s intrinsic width and height to the
intended caller display size while preserving the 794 viewport, then inspect
usages and animator resources for animationGroup. If no animator targets
animationGroup, remove the group wrapper and its pivot/name attributes; also
remove the redundant second path when its full-canvas paint and duplicated glyph
geometry make the first path unused, preserving the final rendered logo.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 710994ac-a28d-40fe-ac58-bdc2691e6523

📥 Commits

Reviewing files that changed from the base of the PR and between c2223ee and d7d94a6.

📒 Files selected for processing (55)
  • android/kotlin-pingonemfa/.gitignore
  • android/kotlin-pingonemfa/README.md
  • android/kotlin-pingonemfa/app/.gitignore
  • android/kotlin-pingonemfa/app/build.gradle.kts
  • android/kotlin-pingonemfa/app/proguard-rules.pro
  • android/kotlin-pingonemfa/app/src/main/AndroidManifest.xml
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/AuthApp.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/MainActivity.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/PingOneSampleApplication.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/DiagnosticLogger.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/PingOneViewModel.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationActionReceiver.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationCancelBus.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationHelper.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationActivity.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationStore.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationViewModel.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/service/PushNotificationService.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/theme/Color.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/theme/Shape.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/theme/Theme.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/theme/Type.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/DiagnosticLogsScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/PushNotificationScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/QrScannerScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/AccountAvatar.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/AccountCard.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ApproveDenyRow.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/BackNavigationTopAppBar.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/LoadingIndicator.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualNumberChallenge.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualPairingPanel.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/NumberChallengeOptions.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/OtpBox.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/util/QrCodeAnalyzer.kt
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_check.xml
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_close.xml
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_launcher_background.xml
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_launcher_foreground.xml
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ic_notification.xml
  • android/kotlin-pingonemfa/app/src/main/res/drawable/ping_logo.xml
  • android/kotlin-pingonemfa/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  • android/kotlin-pingonemfa/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  • android/kotlin-pingonemfa/app/src/main/res/values/colors.xml
  • android/kotlin-pingonemfa/app/src/main/res/values/strings.xml
  • android/kotlin-pingonemfa/app/src/main/res/values/themes.xml
  • android/kotlin-pingonemfa/build.gradle.kts
  • android/kotlin-pingonemfa/gradle.properties
  • android/kotlin-pingonemfa/gradle/libs.versions.toml
  • android/kotlin-pingonemfa/gradle/wrapper/gradle-wrapper.properties
  • android/kotlin-pingonemfa/gradlew
  • android/kotlin-pingonemfa/gradlew.bat
  • android/kotlin-pingonemfa/settings.gradle.kts

Comment thread android/kotlin-pingonemfa/gradle/libs.versions.toml Outdated
Comment thread android/kotlin-pingonemfa/README.md
@coderabbitai coderabbitai Bot mentioned this pull request Aug 5, 2026
New Android Kotlin/Compose sample demonstrating PingOne MFA SDK
integration: QR-code account pairing, TOTP display with live countdown,
and push-notification approve/deny flows (default, number-challenge,
dry-run, and server-cancel).

Key implementation details:

- PingOneSampleApplication performs one-time SDK init and FCM token
  registration in a process-lifetime CoroutineScope, sequentially
  (token registration runs only after initialize() succeeds) so the
  device is always registered for push on first install.

- PushNotificationService forks foreground vs. background on arrival:
  foreground launches PushNotificationActivity directly via startActivity
  (BAL-safe because ProcessLifecycleOwner confirms foreground); background
  posts a high-priority notification with setFullScreenIntent so the OS
  wakes the screen from lock without the service calling startActivity.

- PushNotification is carried as a Parcelable Intent extra so
  PushNotificationActivity survives process death between banner post
  and user tap. PushNotificationStore is narrowed to in-process
  cancel-path coordination only.

- NotificationCancelBus uses MutableSharedFlow(replay=1) so a cancel
  signal emitted before PushNotificationActivity subscribes is not
  dropped.

- PushNotificationViewModel owns approve/deny in viewModelScope so
  in-flight SDK calls survive Activity recreation. onNewIntent swaps
  the displayed notification via Compose state (no recreate()) and
  calls resetState() to clear stale dialog results atomically.

- OTP countdown uses a SystemClock.elapsedRealtime() deadline anchored
  at SDK response time and stored in ViewModel, so the counter resumes
  at the true remaining time after navigation rather than resetting to
  30 s. otpVersion counter guarantees LaunchedEffect restarts on every
  generateOtp() outcome including failure.

- QrCodeAnalyzer is a single remembered instance with DisposableEffect
  cleanup; scanned guard uses AtomicBoolean for thread-safe
  compare-and-set between camera executor and composition threads.
  CameraProvider binding uses addListener(ContextCompat.getMainExecutor)
  to avoid blocking the main thread and satisfy CameraX @mainthread
  assertions.

- All strings externalised to strings.xml; MIT copyright headers on
  every source file; DiagnosticLogger StateFlow updates use update()
  for atomicity.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/service/PushNotificationService.kt (1)

61-70: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Keep FCM work within the callback execution window.

FirebaseMessagingService.onMessageReceived() and onNewToken() return without keeping the Android process alive, and scope.launch in both callbacks leaves PingOne MFA work running after the callback ends. Process these calls synchronously before returning, or schedule durable/long-running work with WorkManager instead of relying on the instance-owned coroutine scope.

🤖 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
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/service/PushNotificationService.kt`
around lines 61 - 70, Update the FirebaseMessagingService callbacks that invoke
PingOneMFA processing to complete the work before returning, replacing the
instance-owned scope.launch usage with synchronous execution, or enqueueing
durable long-running work through WorkManager. Apply this to both
onMessageReceived() and onNewToken(), preserving the existing success and
failure handling.
🤖 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.

Outside diff comments:
In
`@android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/service/PushNotificationService.kt`:
- Around line 61-70: Update the FirebaseMessagingService callbacks that invoke
PingOneMFA processing to complete the work before returning, replacing the
instance-owned scope.launch usage with synchronous execution, or enqueueing
durable long-running work through WorkManager. Apply this to both
onMessageReceived() and onNewToken(), preserving the existing success and
failure handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a4a4daa6-5d28-4001-a173-e25cb7580bf2

📥 Commits

Reviewing files that changed from the base of the PR and between d7d94a6 and a8c9774.

📒 Files selected for processing (20)
  • android/kotlin-pingonemfa/app/build.gradle.kts
  • android/kotlin-pingonemfa/app/google-services.json
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/MainActivity.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/PingOneSampleApplication.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/DiagnosticLogger.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/PingOneViewModel.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationActionReceiver.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationHelper.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationActivity.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationViewModel.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/service/PushNotificationService.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/AccountsScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/PushNotificationScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/QrScannerScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/AccountAvatar.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualNumberChallenge.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/util/QrCodeAnalyzer.kt
  • android/kotlin-pingonemfa/gradle/libs.versions.toml
  • android/kotlin-pingonemfa/gradle/wrapper/gradle-wrapper.properties
🚧 Files skipped from review as they are similar to previous changes (16)
  • android/kotlin-pingonemfa/gradle/wrapper/gradle-wrapper.properties
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationActivity.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/QrGrid.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/MainActivity.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/PushNotificationViewModel.kt
  • android/kotlin-pingonemfa/gradle/libs.versions.toml
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/PushNotificationScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/AccountAvatar.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/components/ManualNumberChallenge.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/notification/NotificationHelper.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/PingOneSampleApplication.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/PingOneViewModel.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/ui/QrScannerScreen.kt
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/data/DiagnosticLogger.kt
  • android/kotlin-pingonemfa/app/build.gradle.kts
  • android/kotlin-pingonemfa/app/src/main/java/com/pingidentity/samples/pingonesample/util/QrCodeAnalyzer.kt

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant