Skip to content

Conversation

@theMr17
Copy link
Owner

@theMr17 theMr17 commented Jun 14, 2025

Summary by CodeRabbit

  • New Features

    • Introduced notification permission handling for Android 13+ devices, including user prompts and guidance for granting notification access.
    • Added a notification management system with categorized channels for improved notification delivery and user control.
    • Added user interface elements and messages to explain notification permissions, including rationale and settings prompts.
  • Improvements

    • Enhanced app startup to initialize notification channels automatically.
    • Updated dependencies to support notification permission features.
  • Tests

    • Added UI tests and test utilities for notification permission scenarios.
  • Chores

    • Expanded automated testing to cover additional Android versions.

Issue Reference

Essential Checklist

  • The PR title starts with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...").
  • The PR does not contain any unnecessary code changes from Android Studio.
  • The PR is made from a branch that is not called "main" and is up-to-date with "main".

@coderabbitai
Copy link

coderabbitai bot commented Jun 14, 2025

Walkthrough

This change introduces Android system notification support. It adds notification permission handling, defines a notification channel, implements a notification handler, and integrates permission-aware UI components. The app now requests and manages notification permissions, creates notification channels at startup, and can display notifications with actions, supporting Android 13+ permission requirements.

Changes

File(s) Change Summary
app/src/main/AndroidManifest.xml Added android.permission.POST_NOTIFICATIONS permission.
app/build.gradle.kts, gradle/libs.versions.toml Added Accompanist permissions library dependency and version.
app/src/main/java/com/notifier/app/GithubNotifierApp.kt Extended GithubNotifierApp to inject and initialize NotificationHandler on app start.
app/src/main/java/com/notifier/app/core/domain/notification/AppNotificationChannel.kt Added AppNotificationChannel enum for notification channel definitions.
app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt Added NotificationHandler class to manage notification creation and channel registration.
app/src/main/java/com/notifier/app/MainActivity.kt Wrapped notification screen in WithNotificationPermission for permission handling.
app/src/main/java/com/notifier/app/core/presentation/notification/NotificationPermissionState.kt Introduced interface modeling notification permission state.
app/src/main/java/com/notifier/app/core/presentation/notification/WithNotificationPermission.kt Added composable for permission-aware UI and prompts.
app/src/main/java/com/notifier/app/core/presentation/notification/rememberNotificationPermissionState.kt Added composable to manage and expose notification permission state using Accompanist.
app/src/main/res/values/strings.xml Added string resources for notification permission rationale and prompts.
app/src/androidTest/java/com/notifier/app/core/presentation/notification/FakeNotificationPermissionState.kt Added a fake permission state class for UI testing.
app/src/androidTest/java/com/notifier/app/core/presentation/notification/WithNotificationPermissionTest.kt Added UI tests for notification permission prompts and flows.
.github/workflows/test.yml Expanded instrumented test matrix to include API 33.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MainActivity
    participant WithNotificationPermission
    participant NotificationHandler
    participant SystemNotificationManager

    User->>MainActivity: Launches app
    MainActivity->>WithNotificationPermission: Navigates to notification screen
    WithNotificationPermission->>WithNotificationPermission: Checks notification permission
    alt Permission not granted
        WithNotificationPermission->>User: Shows rationale or denied prompt
        User->>WithNotificationPermission: Grants permission or opens settings
    end
    alt Permission granted
        WithNotificationPermission->>NotificationHandler: Triggers notification (when needed)
        NotificationHandler->>SystemNotificationManager: Posts notification with PendingIntent
        User->>SystemNotificationManager: Taps notification
        SystemNotificationManager->>App: Opens app or URL
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Handle notification permissions (#20)
Use NotificationManager to post system notifications (#20)
Configure a notification channel for API 26+ with importance, sound, vibration (#20)
Notification displays title, content, and tap action (opens app or URL) (#20)
Use PendingIntent to open app at correct screen or URL (#20)
Ensure compatibility across Android versions (#20)
Handle background limitations on Android 12+ (foreground services, etc.) (#20) No explicit handling of background limitations or foreground service is visible in this PR.

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Possibly related PRs

Poem

In the meadow of code, a notifier hops,
Now with system alerts, it never stops!
Channels created, permissions in tow,
When GitHub calls, your phone will glow.
With a tap or a click, you’ll always know—
🐇✨ Notifications, ready to go!

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai bot changed the title Fix #20: @coderabbitai Fix #20: Add Android system notification support with notification channels Jun 14, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/src/main/AndroidManifest.xml (1)

5-6: Consider declaring the permission with uses-permission-sdk-33 to avoid confusing pre-T devices.

<uses-permission-sdk-33 android:name="android.permission.POST_NOTIFICATIONS" />

This prevents the permission from being shown as a normal (always-granted) permission on API < 33, keeping Play-Store policy noise down.

app/src/main/java/com/notifier/app/core/domain/notification/AppNotificationChannel.kt (1)

8-12: Hard-coded strings hurt localisation.
Move displayName and description into strings.xml and reference them via @StringRes or resource IDs so the app is translatable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3d5054 and 39be3c9.

📒 Files selected for processing (4)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/com/notifier/app/GithubNotifierApp.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/domain/notification/AppNotificationChannel.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: unit_tests
  • GitHub Check: instrumented_tests (29)
  • GitHub Check: build
  • GitHub Check: ci/bitrise/a7464fec-c4a9-4530-ba90-de9098cb5cd8/pr
🔇 Additional comments (4)
app/src/main/AndroidManifest.xml (1)

6-6: POST_NOTIFICATIONS requires compileSdk 33+. Make sure your build is already bumped.
If the project is still on compileSdk 32 or lower, the build will fail with attribute not found for this permission.

app/src/main/java/com/notifier/app/core/domain/notification/AppNotificationChannel.kt (1)

3-13: entries requires Kotlin 1.9 – please verify toolchain.
AppNotificationChannel.entries (used in NotificationHandler) is generated only when compiling with Kotlin 1.9+. Older AGP stacks (< 8.1) ship 1.8.x and will not compile.

app/src/main/java/com/notifier/app/GithubNotifierApp.kt (1)

13-17: createNotificationChannels() will crash on API < 26 unless guarded internally.
The call path is unconditional; ensure the method itself performs an SDK_INT >= 26 check (see comment in NotificationHandler).

app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt (1)

38-38: Same entries concern as above – compilation depends on Kotlin 1.9.
If upgrading Kotlin is not an option, replace with enumValues<AppNotificationChannel>().

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionBanner.kt (2)

32-40: Expose a Modifier parameter for easier reuse

WithNotificationPermission always wraps its children in a bare Column, which limits layout flexibility (e.g. alignment, padding).
Add an optional modifier: Modifier = Modifier to let callers decide how the wrapper should be placed without introducing another composable layer.

-@Composable
-fun WithNotificationPermission(
-    content: @Composable () -> Unit,
-) {
-    Column {
+@Composable
+fun WithNotificationPermission(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit,
+) {
+    Column(modifier = modifier) {
         NotificationPermissionHandler()
         content()
     }
 }

89-90: Minor nit: inline lambda instead of extra val

val onClick = … is used once; inlining it in TextButton(onClick = …) removes a disposable allocation and shrinks the scope.

-    val onClick = if (shouldShowRationale) onRequestPermission else onOpenSettings
-
     Column(
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39be3c9 and 60c05f8.

📒 Files selected for processing (4)
  • app/build.gradle.kts (1 hunks)
  • app/src/main/java/com/notifier/app/MainActivity.kt (2 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionBanner.kt (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • app/build.gradle.kts
  • gradle/libs.versions.toml
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/src/main/java/com/notifier/app/MainActivity.kt (2)
app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionBanner.kt (1)
  • WithNotificationPermission (32-40)
app/src/main/java/com/notifier/app/notification/presentation/NotificationRoute.kt (1)
  • NotificationRoute (9-12)
app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionBanner.kt (1)
app/src/main/java/com/notifier/app/ui/theme/Theme.kt (1)
  • GitHubNotifierTheme (39-61)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: unit_tests
  • GitHub Check: instrumented_tests (29)
  • GitHub Check: build
  • GitHub Check: ci/bitrise/a7464fec-c4a9-4530-ba90-de9098cb5cd8/pr

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionPrompt.kt (3)

58-66: Avoid unnecessary LaunchedEffect restarts by scoping the key more narrowly

permissionState.status is a data class whose instance is recreated on every recomposition; using it as the sole key forces the effect to cancel & re-launch every frame, even when nothing meaningful has changed.
Limit the key to something stable (e.g. permissionState.status.isGranted) or even Unit.

-    LaunchedEffect(permissionState.status) {
+    LaunchedEffect(permissionState.status.isGranted, hasRequested) {

This preserves the single–launch guarantee while reducing churn.


76-85: Harden “open settings” intent against non-activity contexts & missing handlers

LocalContext.current may not always be an Activity; starting an explicit settings intent from an application context requires the FLAG_ACTIVITY_NEW_TASK.
Additionally, startActivity can throw if no handler exists (rare but possible on OEM ROMs).

-                val intent = Intent(
-                    Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
-                    Uri.fromParts(
-                        /* scheme = */ "package",
-                        /* ssp = */ context.packageName,
-                        /* fragment = */ null
-                    )
-                )
-                context.startActivity(intent)
+                runCatching {
+                    Intent(
+                        Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+                        Uri.fromParts("package", context.packageName, null)
+                    ).apply {
+                        addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    }.let(context::startActivity)
+                }

Prevents a potential ActivityNotFoundException and guarantees the launch succeeds from any context.


42-46: Minor UI concern: wrapper Column may impose unintended padding/positioning

Placing NotificationPermissionHandler() above content() inside a Column will vertically stack the permission banner and the screen’s root composable, which can shift the entire UI down when the banner is invisible (granted case).

If the banner should overlay rather than push content, consider using a Box and draw the banner conditionally on top, or move the banner inside a Scaffold’s snackbar/host slot.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9cdf794 and 8001a97.

📒 Files selected for processing (2)
  • app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionPrompt.kt (1 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/res/values/strings.xml
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: instrumented_tests (29)
  • GitHub Check: unit_tests
  • GitHub Check: build
  • GitHub Check: ci/bitrise/a7464fec-c4a9-4530-ba90-de9098cb5cd8/pr
🔇 Additional comments (1)
app/src/main/java/com/notifier/app/core/presentation/NotificationPermissionPrompt.kt (1)

1-160: Overall implementation looks solid

Kudos on the clear separation of concerns, Material 3 theming, and preview support. The runtime logic correctly respects API < 33 devices and handles the three notification-permission scenarios.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
app/src/main/java/com/notifier/app/core/presentation/WithNotificationPermission.kt (3)

91-102: Avoid silently swallowing startActivity failures – at least log the exception
runCatching { … }.let {} suppresses every throwable, making runtime problems hard to diagnose (e.g., ActivityNotFoundException, malformed Intent).
Capture the failure and record it (Logcat / Crashlytics) so that unexpected issues surface during QA and in production.

-                runCatching {
+                runCatching {-                }
+                }.onFailure { Log.e("NotificationPermission", "Unable to open app settings", it) }

71-80: Guard the LaunchedEffect with a stable key to avoid recomposition churn
Using the whole permissionState.status object as the key recreates the coroutine whenever any internal field instance changes.
A lighter key such as permissionState.status.isGranted (or even Unit) combined with the explicit hasRequested flag is sufficient and reduces unnecessary launches:

-    LaunchedEffect(permissionState.status) {
+    LaunchedEffect(permissionState.status.isGranted) {

This keeps the behaviour identical while avoiding redundant coroutine restarts.


50-54: Consider Box+align for overlay prompts
Placing the prompt and screen content in a Column means the permission banner always consumes vertical space and may push critical UI elements off-screen.
Wrapping them in a Box and aligning the prompt to Alignment.TopCenter lets the banner overlay the screen non-intrusively:

-    Column {
-        NotificationPermissionHandler()
-        content()
-    }
+    Box {
+        content()
+        NotificationPermissionHandler()
+    }

This mirrors common in-app banner patterns (e.g., Snackbars) and avoids layout shifts.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 313a8a0 and 40dec2b.

📒 Files selected for processing (1)
  • app/src/main/java/com/notifier/app/core/presentation/WithNotificationPermission.kt (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/src/main/java/com/notifier/app/core/presentation/WithNotificationPermission.kt (1)
app/src/main/java/com/notifier/app/ui/theme/Theme.kt (1)
  • GitHubNotifierTheme (39-61)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: instrumented_tests (29)
  • GitHub Check: build
  • GitHub Check: ci/bitrise/a7464fec-c4a9-4530-ba90-de9098cb5cd8/pr

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (7)
.github/workflows/test.yml (1)

37-37: Consider disabling fail-fast for the matrix

If one API level fails early, the other job is cancelled by default. Explicitly adding

strategy:
  fail-fast: false

keeps both results and shortens the feedback loop when only one level flakes.

app/src/main/java/com/notifier/app/core/presentation/notification/rememberNotificationPermissionState.kt (1)

25-38: Return value isn’t remember-stable across recompositions

Creating a new anonymous object on every recomposition can invalidate remember { … } callers or cause unnecessary recompositions higher up the tree. Wrap the object in remember so its reference remains stable while its delegated values still update.

-    return object : NotificationPermissionState {
+    return remember(state) {
+        object : NotificationPermissionState {
             override val shouldShowRationale: Boolean
                 get() = state.status.shouldShowRationale

             override val isGranted: Boolean
                 get() = state.status.isGranted

             override fun requestPermission() {
                 state.launchPermissionRequest()
             }
-    }
+        }
+    }
app/src/androidTest/java/com/notifier/app/core/presentation/notification/WithNotificationPermissionTest.kt (1)

31-38: Hard-coded strings couple tests to UI copy

Using literal text makes the tests fragile when wording changes in strings.xml. Consider injecting the strings via stringResource(R.string.xxx) in the composable and referencing the same IDs in tests (e.g., by reading them from the context) to decouple tests from copy tweaks.

app/src/main/java/com/notifier/app/core/presentation/notification/WithNotificationPermission.kt (4)

45-57: Expose a modifier for greater composability flexibility

The wrapper composable hard-codes an internal Column without exposing a Modifier. Call-sites cannot adjust padding, scrolling behaviour, or test tags. A tiny signature change preserves binary compatibility while unlocking far more use-cases.

-@Composable
-fun WithNotificationPermission(
-    permissionState: NotificationPermissionState? = null,
-    content: @Composable () -> Unit,
-) {
-    Column {
+@Composable
+fun WithNotificationPermission(
+    modifier: Modifier = Modifier,
+    permissionState: NotificationPermissionState? = null,
+    content: @Composable () -> Unit,
+) {
+    Column(modifier = modifier) {

88-101: Avoid swallowing failures when opening Settings

runCatching { … } hides exceptions and gives no signal if the Intent cannot be resolved (rare on AOSP-only devices/emulators).
Logging (or at least printing) the error makes future bug reports diagnosable.

-runCatching {
+runCatching {
   … startActivity(…)
-}
+}.onFailure { throwable ->
+    Log.e("NotificationPermission", "Unable to open app settings", throwable)
+}

(Replace Log with your preferred logger.)


122-134: message / actionButtonText recomputed on every recomposition

Both derived strings are pure functions of shouldShowRationale; extracting them with remember(shouldShowRationale) avoids unnecessary work and allocations—particularly useful when this prompt sits in an always-visible scaffold.

val (message, actionButtonText) = remember(shouldShowRationale) {
    if (shouldShowRationale) {
        stringResource(R.string.notification_permission_rationale_message) to
            stringResource(R.string.notification_permission_allow_button_text)
    } else {
        stringResource(R.string.notification_permission_denied_message) to
            stringResource(R.string.notification_permission_settings_button_text)
    }
}

181-195: Add a preview for the full WithNotificationPermission wrapper

Current previews cover only the prompt. A snapshot of the wrapper simplifies visual regression checks and demonstrates default styling once the modifier change (see above) is applied.
Consider:

@PreviewLightDark
@Composable
private fun WithNotificationPermissionPreview() {
    GitHubNotifierTheme {
        WithNotificationPermission(
            permissionState = FakeNotificationPermissionState(
                granted = false,
                shouldShowRationale = true
            )
        ) {
            Text("Main content goes here")
        }
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b345964 and 9893ff2.

📒 Files selected for processing (7)
  • .github/workflows/test.yml (1 hunks)
  • app/src/androidTest/java/com/notifier/app/core/presentation/notification/FakeNotificationPermissionState.kt (1 hunks)
  • app/src/androidTest/java/com/notifier/app/core/presentation/notification/WithNotificationPermissionTest.kt (1 hunks)
  • app/src/main/java/com/notifier/app/MainActivity.kt (2 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/notification/NotificationPermissionState.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/notification/WithNotificationPermission.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/notification/rememberNotificationPermissionState.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/com/notifier/app/MainActivity.kt
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: ci/bitrise/a7464fec-c4a9-4530-ba90-de9098cb5cd8/pr
🔇 Additional comments (3)
.github/workflows/test.yml (1)

37-37: Expanded test matrix to API 33 looks good

Covering Android 13 in CI aligns with the new notification-permission code and should prevent regressions.

app/src/androidTest/java/com/notifier/app/core/presentation/notification/FakeNotificationPermissionState.kt (1)

12-18: Lean, purposeful test double

The fake cleanly fulfills the interface without extra baggage—nice.

app/src/main/java/com/notifier/app/core/presentation/notification/NotificationPermissionState.kt (1)

9-30: Solid abstraction enables decoupled UI & logic

The interface is minimal yet sufficient for real and fake implementations—good design.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/test.yml (1)

48-52: DRY up Java setup steps

The JDK setup for both unit_tests and instrumented_tests is identical. Consider using a YAML anchor or a reusable workflow to avoid duplication and ease future updates.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9893ff2 and b7f2bc7.

📒 Files selected for processing (1)
  • .github/workflows/test.yml (3 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/test.yml

38-38: expecting a single ${{...}} expression or array value for matrix variations, but found plain text node

(syntax-check)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: ci/bitrise/a7464fec-c4a9-4530-ba90-de9098cb5cd8/pr
🔇 Additional comments (3)
.github/workflows/test.yml (3)

57-58: Approve: gradlew execution permission

Granting execute permissions on gradlew before any invocations ensures the wrapper can run correctly in this job.


73-79: Approve: AVD snapshot creation settings

The added target: google_apis, arch: x86_64, force-avd-creation: true, disable-animations: true, and snapshot script correctly prepare and cache emulator images for both API levels.


85-86: Approve: emulator target and architecture

Specifying the same target and arch in the test run ensures consistency with the snapshot creation step.

@theMr17 theMr17 changed the title Fix #20: Add Android system notification support with notification channels Fix #20, #24: Add Android system notification support with notification channels Jun 18, 2025
@theMr17
Copy link
Owner Author

theMr17 commented Jun 20, 2025

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Jun 20, 2025

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/src/main/java/com/notifier/app/MainActivity.kt (1)

95-99: Permission banner timing issue remains unaddressed.

The integration of WithNotificationPermission is architecturally sound, but the previously identified timing issue where the permission banner appears before the system dialog still needs to be resolved to avoid user confusion.

🧹 Nitpick comments (4)
app/src/main/AndroidManifest.xml (1)

5-7: Permission declaration is correct—consider adding tools:targetApi
You’ve correctly added the POST_NOTIFICATIONS permission for Android 13+. To suppress lint warnings on lower API levels, you can add tools:targetApi="33" on this line:

<uses-permission
    android:name="android.permission.POST_NOTIFICATIONS"
    tools:targetApi="33" />
app/build.gradle.kts (1)

104-107: Add accompanist-permissions to instrumentation tests
Since your UI tests under androidTest invoke WithNotificationPermission, you’ll need the Accompanist permissions library on that classpath. Consider adding:

androidTestImplementation(libs.accompanist.permissions)

to ensure your instrumentation tests compile and run without missing-dependency errors.

.github/workflows/test.yml (1)

54-65: Consolidate duplicated setup steps
You’ve duplicated the JDK setup and chmod +x gradlew steps in both jobs. Consider extracting these into a reusable composite action or a shared job definition to adhere to DRY principles.

app/src/androidTest/java/com/notifier/app/core/presentation/notification/WithNotificationPermissionTest.kt (1)

34-44: Consider using string resources in tests for consistency.

The tests use hardcoded strings that should match the actual string resources. Consider extracting these into a test utility or using the string resources directly to prevent potential inconsistencies if the actual strings change.

Example approach:

// Instead of hardcoded strings, use:
composeTestRule
    .onNodeWithText(
-        "To deliver GitHub notifications like pull requests, issues, and mentions, " +
-                "we need notification access. Please allow it."
+        context.getString(R.string.notification_permission_rationale_message)
    )
    .assertIsDisplayed()

Also applies to: 66-77

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3d5054 and ef23e4c.

📒 Files selected for processing (14)
  • .github/workflows/test.yml (2 hunks)
  • app/build.gradle.kts (1 hunks)
  • app/src/androidTest/java/com/notifier/app/core/presentation/notification/FakeNotificationPermissionState.kt (1 hunks)
  • app/src/androidTest/java/com/notifier/app/core/presentation/notification/WithNotificationPermissionTest.kt (1 hunks)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/com/notifier/app/GithubNotifierApp.kt (1 hunks)
  • app/src/main/java/com/notifier/app/MainActivity.kt (2 hunks)
  • app/src/main/java/com/notifier/app/core/domain/notification/AppNotificationChannel.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/notification/NotificationPermissionState.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/notification/WithNotificationPermission.kt (1 hunks)
  • app/src/main/java/com/notifier/app/core/presentation/notification/rememberNotificationPermissionState.kt (1 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
  • gradle/libs.versions.toml (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt (1)
Learnt from: theMr17
PR: theMr17/github-notifier#23
File: app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt:37-53
Timestamp: 2025-06-14T12:33:29.399Z
Learning: The github-notifier Android app has minSdkVersion set to 26, so API-level guards for Android 8.0+ features like NotificationChannel are not required.
🔇 Additional comments (18)
gradle/libs.versions.toml (1)

21-21: Accompanist permissions dependency declared correctly
The new accompanistPermissions = "0.37.3" version and corresponding accompanist-permissions library entry align with the project’s version catalog format and support the runtime permission flow.

Also applies to: 55-55

app/src/main/res/values/strings.xml (1)

17-20: Notification permission strings added
The rationale, denial message, and button labels for the notification-permission dialog are well-defined and follow the existing naming conventions.

.github/workflows/test.yml (4)

35-44: Expanded instrumented test matrix configuration is correct
The include block now covers API levels 29 & 33 with x86/x86_64, and fail-fast is correctly placed outside matrix. This ensures broad coverage without syntax errors.


73-74: AVD cache key enhancement approved
Including both ${{ matrix.api-level }} and ${{ matrix.arch }} in the cache key is a solid approach to avoid collisions and ensure correct snapshot reuse per device config.


80-85: AVD creation parameters align with requirements
Specifying arch, target: google_apis, force-avd-creation, and disabling animations ensures consistent emulator snapshots and fast cold boots across matrix entries.


91-96: Run-in-AVD test execution is sound
Passing api-level, arch, target, and calling ./gradlew connectedDebugAndroidTest --daemon matches the snapshot creation step and leverages the matrix correctly for instrumented tests.

app/src/main/java/com/notifier/app/core/domain/notification/AppNotificationChannel.kt (1)

11-33: Well-designed notification channel enum.

The enum structure is clean and follows Android notification channel best practices. The comprehensive documentation and extensible design will facilitate future channel additions.

app/src/androidTest/java/com/notifier/app/core/presentation/notification/FakeNotificationPermissionState.kt (1)

12-18: Clean test double implementation.

The fake implementation properly serves its testing purpose with clear documentation and appropriate no-op behavior for the permission request method.

app/src/main/java/com/notifier/app/core/presentation/notification/NotificationPermissionState.kt (1)

9-30: Well-designed permission state interface.

The interface provides a clean abstraction for notification permission handling with comprehensive documentation. The three-member design captures all essential aspects of Android permission state management.

app/src/main/java/com/notifier/app/GithubNotifierApp.kt (1)

9-18: Proper notification channel initialization.

The implementation correctly initializes notification channels in the Application's onCreate() method, ensuring they're available throughout the app's lifecycle. The dependency injection pattern is properly implemented.

app/src/main/java/com/notifier/app/core/presentation/notification/rememberNotificationPermissionState.kt (1)

23-42: Well-implemented permission state management.

The implementation correctly handles Android 13+ notification permissions using the Accompanist library. The @RequiresApi annotation, proper use of remember() for state management, and clean delegation to the underlying permission state follow best practices.

app/src/main/java/com/notifier/app/core/domain/notification/NotificationHandler.kt (2)

37-65: Solid notification implementation with proper Android patterns.

The showNotification method correctly uses PendingIntent.FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE flags and follows standard notification building practices. The implementation properly handles notification display with appropriate priority and auto-cancel behavior.


73-89: Channel creation correctly implemented for the app's minSdkVersion.

Based on the retrieved learning that this app has minSdkVersion = 26, the NotificationChannel creation doesn't require API level guards since all supported devices will have this API available.

app/src/main/java/com/notifier/app/core/presentation/notification/WithNotificationPermission.kt (4)

45-58: Clean conditional permission handling for Android 13+.

The implementation correctly checks the API level and only applies permission handling on Android 13+ devices. The structure allows the main content to always be displayed regardless of permission state.


75-80: Smart automatic permission request logic.

The LaunchedEffect correctly implements the one-time automatic permission request when the permission hasn't been requested and no rationale is needed. Using hasRequested with rememberSaveable ensures proper state persistence across configuration changes.


90-108: Robust error handling for settings navigation.

The runCatching block properly handles potential failures when opening app settings, with appropriate logging. This prevents crashes if the settings intent cannot be resolved.


124-167: Well-designed adaptive permission prompt UI.

The prompt correctly adapts its message and action based on the shouldShowRationale state, providing appropriate user guidance for both scenarios. The Material 3 theming and layout structure follow good design practices.

app/src/androidTest/java/com/notifier/app/core/presentation/notification/WithNotificationPermissionTest.kt (1)

14-112: Comprehensive test coverage for permission states.

The tests thoroughly cover all permission scenarios (granted, denied with rationale, permanently denied) and properly verify UI behavior. The use of @SdkSuppress(minSdkVersion = 33) correctly matches the Android 13+ requirement, and the FakeNotificationPermissionState provides good test isolation.

@theMr17 theMr17 merged commit ce106f8 into main Jun 20, 2025
11 of 14 checks passed
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.

[Enhancement]: Improve Instrumented Test Workflow Matrix & Failure Handling [Feature Request]: Setup Android System Notification

2 participants