Skip to content

Task/sdk 4887/sample custom template + app_inbox #833

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 27, 2025

Conversation

Anush-Shand
Copy link
Contributor

@Anush-Shand Anush-Shand commented Jun 18, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a custom inbox screen built with Jetpack Compose, accessible from the main menu.
    • Added support for custom in-app message templates, including interstitial dialogs, copy-to-clipboard, and URL confirmation dialogs.
    • Integrated overlay dialogs and confirmation modals using Compose UI components.
    • Enhanced inbox management with new Compose-based screens and actions (view, mark as read, delete, etc.).
  • Improvements

    • Updated build configuration to support Jetpack Compose and related dependencies.
    • Refined the main activity and UI layout to support Compose overlays and dialogs.
    • Added new commands to sync custom templates and open the Compose inbox screen.
  • Bug Fixes

    • Improved handling of intent parameters and removed unused imports and annotations for cleaner code.
  • Chores

    • Added new ViewModel classes and providers to manage dialog and inbox states in Compose environments.
    • Centralized ViewModel access for custom template presenters to support Compose integration.

Copy link
Contributor

coderabbitai bot commented Jun 18, 2025

Warning

Rate limit exceeded

@Anush-Shand has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 18 minutes and 59 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2d2629f and a3207db.

📒 Files selected for processing (1)
  • sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenViewModel.kt (2 hunks)
## Walkthrough

This update introduces Jetpack Compose UI integration and custom in-app messaging templates to the Android sample project. It adds new Compose-based activities, screens, dialogs, and ViewModels, updates the Gradle configuration to support Compose, and registers custom templates with CleverTap. The codebase now supports Compose-driven inbox and custom dialog interactions.

## Changes

| File(s)                                                                                     | Change Summary |
|--------------------------------------------------------------------------------------------|---------------|
| sample/build.gradle                                                                        | Refactored to use the `plugins` block, enabled Compose build features, and added Compose and Glide dependencies. |
| sample/src/main/AndroidManifest.xml                                                        | Added `.ui.inbox.CustomInboxComposeActivity` activity declaration with Compose theme. |
| sample/src/main/java/com/clevertap/demo/HomeScreenActivity.kt                              | Integrated Compose overlay dialog, added ViewModels for custom templates and URL confirmation, and updated intent handling. |
| sample/src/main/java/com/clevertap/demo/MyApplication.kt                                   | Registered custom in-app templates with CleverTap and performed minor code cleanups. |
| sample/src/main/java/com/clevertap/demo/ViewModelFactory.kt                                | Extended factory to support new Compose-related ViewModels. |
| sample/src/main/java/com/clevertap/demo/WebViewActivity.kt                                 | Removed unused import, updated method signatures, and cleaned up annotations. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateDefinitions.kt    | **New:** Defines custom templates, presenters, and argument handling for in-app messages and functions. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt                | **New:** Implements a reusable Compose overlay dialog component and supporting UI elements. |
| sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxComposeActivity.kt             | **New:** Adds a Compose-based inbox activity with ViewModel and Compose UI setup. |
| sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxScreen.kt                      | **New:** Implements a Compose screen for displaying and interacting with inbox messages. |
| sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModel.kt                   | **New:** ViewModel for managing inbox state and actions, implementing CleverTap inbox listener. |
| sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModelContract.kt           | **New:** Interface defining the contract for inbox ViewModel behavior and properties. |
| sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenFragment.kt                      | Added logic to launch the new Compose inbox activity from a command. |
| sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenModel.kt                         | Added new features to the list data: Compose inbox and custom templates. |
| sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenViewModel.kt                     | Added command handling for syncing templates and testing custom template dialog; added test dialog logic. |
| sample/src/main/res/layout/home_screen_activity.xml                                        | Updated layout to include a ComposeView overlay for Compose dialogs. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt  | **New:** ViewModel for managing custom interstitial dialog state and behavior in Compose. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModelProvider.kt | **New:** Singleton provider for custom template ViewModel, enabling access outside standard lifecycle. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmDialog.kt         | **New:** Compose dialog for confirming URL actions with user interaction. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModel.kt      | **New:** ViewModel for managing the state of the URL confirmation dialog. |
| sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModelProvider.kt | **New:** Singleton provider for OpenUrlConfirmViewModel, for use outside standard lifecycle. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant HomeScreenActivity
    participant CustomTemplateViewModel
    participant OpenUrlConfirmViewModel
    participant ComposeView
    participant OverlayDialog
    participant OpenUrlConfirmDialog

    User->>HomeScreenActivity: Triggers custom template action
    HomeScreenActivity->>CustomTemplateViewModel: showDialog(...)
    CustomTemplateViewModel->>ComposeView: Update dialog state
    ComposeView->>OverlayDialog: Render dialog UI

    User->>OverlayDialog: Clicks primary/secondary button
    OverlayDialog->>CustomTemplateViewModel: Invoke callback/hideDialog()
    CustomTemplateViewModel->>ComposeView: Update dialog state

    User->>HomeScreenActivity: Triggers open URL confirm
    HomeScreenActivity->>OpenUrlConfirmViewModel: showDialog(url, ...)
    OpenUrlConfirmViewModel->>ComposeView: Update dialog state
    ComposeView->>OpenUrlConfirmDialog: Render confirm dialog

    User->>OpenUrlConfirmDialog: Clicks Yes/No
    OpenUrlConfirmDialog->>OpenUrlConfirmViewModel: onConfirm/onCancel
    OpenUrlConfirmViewModel->>ComposeView: Update dialog state
sequenceDiagram
    participant User
    participant HomeScreenFragment
    participant CustomInboxComposeActivity
    participant CustomInboxViewModel
    participant InboxScreen

    User->>HomeScreenFragment: Selects "Open Compose Inbox Screen"
    HomeScreenFragment->>CustomInboxComposeActivity: Start activity
    CustomInboxComposeActivity->>CustomInboxViewModel: Initialize ViewModel
    CustomInboxViewModel->>InboxScreen: Provide inbox data
    InboxScreen->>User: Display inbox messages and actions
    User->>InboxScreen: Click/View/Mark Read/Delete message
    InboxScreen->>CustomInboxViewModel: Handle action
    CustomInboxViewModel->>InboxScreen: Update state
Loading

Possibly related PRs

Suggested reviewers

  • piyush-kukadiya
  • vasct
  • CTLalit

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=CleverTap/clevertap-android-sdk&utm_content=833):

- 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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@francispereira
Copy link

francispereira commented Jun 18, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

license/snyk check is complete. No issues have been found. (View Details)

code/snyk check is complete. No issues have been found. (View Details)

Copy link

github-actions bot commented Jun 18, 2025

Code Coverage Debug

Metric (instruction) Coverage Threshold Δ Coverage Status
Overall 60.39% 75.0% +0.02%
Changed Files 0.0% 75.0% 0.0%
Report Coverage (O/Ch) Threshold (O/Ch) Δ Coverage (O/Ch) Status (O/Ch)
clevertap-core 60.83% / 0.0% 75.0% / 75.0% +0.02% / 0.0% ❌/✅
clevertap-pushtemplates 42.88% / 0.0% 75.0% / 75.0% 0.0% / 0.0% ❌/✅

Copy link
Contributor

@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: 11

🔭 Outside diff range comments (1)
sample/src/main/java/com/clevertap/demo/WebViewActivity.kt (1)

41-51: Improve type safety when accessing Bundle extras

The current implementation has potential runtime issues when accessing Bundle values. The code assumes that payload["pt_id"] returns a String and payload["notificationId"] is an Int, but Bundle values are of type Any? which could lead to ClassCastException or unexpected behavior.

Consider applying this diff to improve type safety:

-        val payload = intent.extras
-        if (payload?.containsKey("pt_id") == true && payload["pt_id"] =="pt_rating")
-        {
-            val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-            nm.cancel(payload["notificationId"] as Int)
-        }
-        if (payload?.containsKey("pt_id") == true && payload["pt_id"] =="pt_product_display")
-        {
-            val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-            nm.cancel(payload["notificationId"] as Int)
-        }
+        val payload = intent.extras
+        val ptId = payload?.getString("pt_id")
+        val notificationId = payload?.getInt("notificationId", -1)
+        
+        if (ptId != null && notificationId != -1 && (ptId == "pt_rating" || ptId == "pt_product_display")) {
+            val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+            nm.cancel(notificationId)
+        }

This approach:

  • Uses type-safe Bundle methods (getString, getInt)
  • Consolidates the duplicate notification cancellation logic
  • Avoids potential ClassCastException risks
🧹 Nitpick comments (9)
sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxScreen.kt (1)

43-44: Consider using LazyColumn with items() extension for better performance.

The current implementation using items(count = ...) works but could be improved for better performance and type safety.

-LazyColumn(Modifier.fillMaxWidth()) {
-    items(count = viewModel.inboxMessages.size) { index ->
-        val message = viewModel.inboxMessages[index]
+LazyColumn(Modifier.fillMaxWidth()) {
+    items(viewModel.inboxMessages) { message ->

This approach is more efficient and provides better item identity for animations.

sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxComposeActivity.kt (1)

22-29: Consider using the shared ViewModelFactory for consistency.

While the inline factory works, using the shared ViewModelFactory would provide consistency across the app and better maintainability.

-private val customInboxViewModel: CustomInboxViewModel by viewModels {
-    object : ViewModelProvider.Factory {
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            @Suppress("UNCHECKED_CAST")
-            return CustomInboxViewModel() as T
-        }
-    }
-}
+// Import the shared factory
+private val viewModelFactory by lazy { ViewModelFactory(null) }
+private val customInboxViewModel: CustomInboxViewModel by viewModels { viewModelFactory }

However, this requires updating the shared ViewModelFactory to support CustomInboxViewModel creation.

sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenViewModel.kt (1)

779-807: Improve import organization and add parameter validation.

The test method implementation is solid with good error handling and logging. Consider these improvements:

Move the import to the top of the file:

+import com.clevertap.demo.ui.customtemplates.CustomTemplateManager

And update the method:

 private fun testCustomTemplateDialog() {
-    // Import the custom template manager and test directly
     try {
-        val customTemplateManager = com.clevertap.demo.ui.customtemplates.CustomTemplateManager
-        val viewModel = customTemplateManager.getViewModel()
+        val viewModel = CustomTemplateManager.getViewModel()
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateDefinitions.kt (2)

20-24: Consider externalizing long default message string.

The default message string is quite long and embedded in the code. Consider moving it to a string resource for better maintainability and localization support.

         stringArgument(
             "Message",
-            "This is a custom interstitial message that can be quite long and will be displayed in a scrollable view if needed. You can add multiple lines of text here and it will automatically scroll when the content exceeds the available space."
+            context.getString(R.string.default_custom_interstitial_message)
         ) // Message string with default

Then add to strings.xml:

<string name="default_custom_interstitial_message">This is a custom interstitial message that can be quite long and will be displayed in a scrollable view if needed. You can add multiple lines of text here and it will automatically scroll when the content exceeds the available space.</string>

13-16: Add documentation for the public API function.

The createCustomTemplates function is a public API that should be documented to explain its purpose and parameters.

+/**
+ * Creates a set of custom templates for CleverTap in-app messaging.
+ * 
+ * @param customInterPresenter Presenter for custom interstitial template
+ * @param copyToClipboardPresenter Presenter for clipboard copy functionality
+ * @return TemplatesSet containing all defined custom templates
+ */
 fun createCustomTemplates(
     customInterPresenter: CustomInterstitialPresenter,
     copyToClipboardPresenter: CopyToClipboardPresenter
 ) = templatesSet(
sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt (4)

48-48: Document the usage of experimental Glide Compose API.

The ExperimentalGlideComposeApi is being used without documentation about its stability implications.

+// Note: Using ExperimentalGlideComposeApi - this API may change in future Glide versions
+// Consider monitoring Glide releases for API stability updates
 @OptIn(ExperimentalGlideComposeApi::class)

83-83: Consider using relative sizing instead of fixed height fraction.

Using fillMaxHeight(0.7f) might not work well on all screen sizes, especially smaller screens or landscape orientations.

-                .fillMaxHeight(0.7f)
+                .fillMaxHeight()
+                .heightIn(max = 600.dp) // Maximum height with proper constraints

This ensures the dialog doesn't exceed a reasonable maximum height while being more flexible for different screen sizes.


175-178: Improve parameter naming and add default parameter documentation.

The OverlayScreen composable parameters could be better named and documented for clarity.

+/**
+ * Screen-level composable that displays an overlay dialog with semi-transparent background.
+ *
+ * @param dialogConfig Configuration for the dialog content and callbacks
+ * @param showDialog Whether to show the dialog overlay
+ */
 @Composable
 fun OverlayScreen(
     dialogConfig: OverlayDialogConfig? = null,
-    showDialog: Boolean = false,
+    showDialog: Boolean = false
 ) {

184-188: Extract overlay background color to theme or constants.

The semi-transparent black overlay color should be configurable or use theme colors.

+private val OverlayBackgroundColor = Color.Black.copy(alpha = 0.5f)

             Box(
                 modifier = Modifier
                     .fillMaxSize()
-                    .background(Color.Black.copy(alpha = 0.5f))
+                    .background(OverlayBackgroundColor)
             )

Alternatively, use theme colors:

-                    .background(Color.Black.copy(alpha = 0.5f))
+                    .background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f))
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9b023e1 and 845be48.

📒 Files selected for processing (18)
  • sample/build.gradle (4 hunks)
  • sample/src/main/AndroidManifest.xml (1 hunks)
  • sample/src/main/java/com/clevertap/demo/HomeScreenActivity.kt (6 hunks)
  • sample/src/main/java/com/clevertap/demo/MyApplication.kt (5 hunks)
  • sample/src/main/java/com/clevertap/demo/ViewModelFactory.kt (2 hunks)
  • sample/src/main/java/com/clevertap/demo/WebViewActivity.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateDefinitions.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateViewModel.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateViewModelProvider.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxComposeActivity.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxScreen.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModel.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModelContract.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenFragment.kt (2 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenModel.kt (2 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenViewModel.kt (2 hunks)
  • sample/src/main/res/layout/home_screen_activity.xml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: lint-staticChecks-test-build
🔇 Additional comments (35)
sample/src/main/java/com/clevertap/demo/WebViewActivity.kt (1)

39-39: LGTM: Intent parameter made non-nullable

The change from nullable to non-nullable Intent parameter is correct since the Android framework guarantees that onNewIntent will never receive a null Intent.

sample/build.gradle (4)

3-9: LGTM: Modern Gradle plugin configuration

The migration from apply plugin: statements to the plugins block follows current Gradle best practices. Using alias(libs.plugins.compose.compiler) for the Compose compiler plugin is also the recommended approach.


81-81: LGTM: Compose build features enabled

Enabling Compose build features is required for Jetpack Compose functionality.


138-138: LGTM: Glide Compose integration

Adding Glide Compose integration enables seamless image loading in Compose components. The beta version is appropriate for sample/demo applications.


152-158: LGTM: Comprehensive Compose dependency setup

The Compose dependencies are well-structured:

  • Uses Compose BOM for consistent versioning
  • Includes essential Compose UI components
  • Adds necessary lifecycle integration for ViewModels
  • Follows recommended Compose dependency patterns
sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModelContract.kt (1)

5-16: LGTM: Well-designed view model contract

The interface provides a clean contract with:

  • Essential read-only properties for inbox state
  • Standard message interaction operations (click, view, mark read, delete)
  • Proper use of CleverTap's CTInboxMessage type
  • Clear, descriptive function names following Kotlin conventions
sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenFragment.kt (2)

38-38: LGTM: Import addition

Clean import addition for the new Compose inbox activity.


108-108: LGTM: Consistent command handling

The new command "2-16" follows the existing pattern and properly launches the CustomInboxCompose​Activity using an explicit intent. The command string corresponds to the menu structure.

sample/src/main/AndroidManifest.xml (1)

168-171: LGTM: Proper activity declaration

The activity declaration is correctly configured:

  • android:exported="false" is appropriate for internal activities
  • NoActionBar theme is suitable for Compose activities
  • No unnecessary intent filters, maintaining security
sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenModel.kt (2)

37-38: LGTM: Inbox menu extension

The addition of "Open Compose Inbox Screen" to the INBOX category properly extends the existing menu structure and aligns with the command handling in HomeScreenFragment.


115-115: LGTM: New custom templates category

The new "CUSTOM TEMPLATES" category with sync and test dialog options aligns with the PR's objective to introduce custom template functionality.

sample/src/main/res/layout/home_screen_activity.xml (1)

2-17: LGTM! Proper ComposeView integration for overlay functionality.

The layout structure correctly implements the overlay pattern by:

  • Maintaining the existing container FrameLayout for backward compatibility
  • Adding ComposeView as a sibling for overlay rendering
  • Using appropriate dimensions for full-screen coverage

This enables seamless integration of Compose UI elements on top of existing Views.

sample/src/main/java/com/clevertap/demo/ViewModelFactory.kt (2)

7-7: LGTM! Proper import addition.

The import is correctly added for the new CustomTemplateViewModel class.


18-19: LGTM! Consistent ViewModel factory pattern.

The CustomTemplateViewModel creation follows the established factory pattern:

  • Proper type checking with isAssignableFrom
  • Parameterless constructor call consistent with the ViewModel design
  • Maintains consistency with existing factory structure
sample/src/main/java/com/clevertap/demo/MyApplication.kt (5)

6-6: LGTM! Proper import for ClipboardManager.

The ClipboardManager import is correctly added to support the clipboard functionality in custom templates.


29-32: LGTM! Proper imports for custom template components.

The imports are correctly structured for:

  • Custom template presenters
  • Template creation function

223-223: LGTM! Minor formatting improvement.

Removed unnecessary semicolon improves code consistency.


257-257: LGTM! Simplified system service calls.

The direct cast to NotificationManager is more concise and maintains the same functionality.

Also applies to: 261-261


74-81: ```shell
#!/bin/bash

Search for createCustomTemplates declaration and usage in Kotlin files

rg -n "fun createCustomTemplates" -C3 -- *.kt
rg -n "createCustomTemplates" -C3 -- *.kt

Locate AndroidManifest.xml and display its permission entries

fd AndroidManifest.xml --exec sed -n '1,200p' {} ; | grep -i "permission"


</details>
<details>
<summary>sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxScreen.kt (2)</summary>

`15-24`: **LGTM! Well-structured Composable function.**

The function signature and layout structure follow Compose best practices:
- Proper parameter ordering with modifier last
- Clear responsibility separation
- Appropriate use of Column for vertical layout

---

`25-42`: **LGTM! Proper loading state handling.**

The conditional UI rendering correctly handles the loading state with:
- Clear loading message
- Visual progress indicator
- Proper spacing and layout

</details>
<details>
<summary>sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxComposeActivity.kt (3)</summary>

`31-31`: **LGTM! Proper experimental API usage.**

The `@OptIn(ExperimentalMaterial3Api::class)` annotation is correctly used for the TopAppBar API.

---

`35-58`: **LGTM! Well-structured Compose UI setup.**

The Compose UI implementation follows best practices:
- Proper theme wrapping with MaterialTheme
- Correct Surface usage for background
- Well-structured Scaffold with TopAppBar
- Proper padding propagation to content

---

`41-55`: To confirm that all interface members are implemented, let’s list every `override` in the ViewModel:


```shell
#!/bin/bash
# List all overrides in CustomInboxViewModel
rg "override" sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModel.kt
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateViewModelProvider.kt (2)

10-38: Well-implemented singleton pattern with proper thread safety.

The double-checked locking pattern and synchronized methods ensure thread-safe access to the ViewModel instance.


14-16: ```shell
#!/bin/bash

Display the contents of MyApplication.kt to verify ctInstance initialization

sed -n '1,200p' sample/src/main/java/com/clevertap/demo/MyApplication.kt


</details>
<details>
<summary>sample/src/main/java/com/clevertap/demo/ui/main/HomeScreenViewModel.kt (2)</summary>

`32-33`: **LGTM - Event consolidation.**

The change from multiple events to a single "Product viewed" event is clean and the commented line shows intentional removal.

---

`759-768`: **Well-structured new template commands.**

Both commands follow the existing pattern and provide clear functionality - syncing templates and testing dialog behavior.

</details>
<details>
<summary>sample/src/main/java/com/clevertap/demo/HomeScreenActivity.kt (3)</summary>

`52-55`: **Good ViewModel integration pattern.**

The custom factory approach is consistent with the existing codebase and properly passes the CleverTap instance.

---

`81-95`: **Excellent Compose integration with proper lifecycle management.**

The `ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed` ensures proper cleanup, and the state observation pattern is well-implemented.

---

`226-226`: **Verify Intent nullability change impact.**

The parameter change from `Intent?` to `Intent` could be a breaking change if any callers pass null.



```shell
#!/bin/bash
# Check for any onNewIntent implementations or calls that might pass null
rg -A 3 -B 3 "onNewIntent.*null" --type kotlin
rg -A 3 "\.onNewIntent\(" --type kotlin
ast-grep --pattern 'onNewIntent($_)'
sample/src/main/java/com/clevertap/demo/ui/inbox/CustomInboxViewModel.kt (2)

16-32: Excellent Compose state management and initialization.

The use of mutableStateOf and mutableStateListOf with proper encapsulation is well-implemented. The initialization pattern correctly sets up the listener and triggers inbox initialization.


38-49: Good coroutine usage for UI updates.

Using Dispatchers.Main ensures UI state updates occur on the correct thread. The lifecycle event handling is properly implemented.

sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateViewModel.kt (2)

15-28: Well-designed state management with proper encapsulation.

The data class approach for dialog state and the use of mutableStateOf with private setters follows Compose best practices.


37-86: Excellent auto-close implementation with proper job management.

The coroutine-based auto-close functionality is well-implemented with proper job cancellation and user interaction handling.

@Anush-Shand Anush-Shand changed the base branch from develop to task/SDK-4886/custom_inbox_sample_app June 19, 2025 11:03
@Anush-Shand Anush-Shand changed the base branch from task/SDK-4886/custom_inbox_sample_app to develop June 19, 2025 11:03
Copy link
Contributor

@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: 4

♻️ Duplicate comments (3)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateDefinitions.kt (3)

63-63: Add null safety checks for ViewModel access.

The ViewModel retrieval should include null safety checks and error handling to prevent potential crashes.


134-138: Add error handling for clipboard operations.

The clipboard operation lacks error handling and could cause crashes if the operation fails.


88-90: Incomplete onClose implementation - missing ViewModel interaction.

The onClose method only calls context.setDismissed() but doesn't hide the dialog through the ViewModel, which could leave the dialog visible after the template is closed.

 override fun onClose(context: CustomTemplateContext.TemplateContext) {
+    try {
+        val viewModel = CustomTemplateManager.getViewModel()
+        viewModel.hideDialog()
+    } catch (e: Exception) {
+        // Log error but continue with dismissal
+    }
     context.setDismissed()
 }
🧹 Nitpick comments (4)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmDialog.kt (1)

36-36: Use theme colors instead of hardcoded color values.

The dialog uses hardcoded colors that should be replaced with theme colors for better consistency and dark mode support.

-                    containerColor = Color.White
+                    containerColor = MaterialTheme.colorScheme.surface

Consider replacing other hardcoded colors with appropriate theme colors like MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.onSurface, etc.

Also applies to: 74-74, 80-80, 102-102, 121-121

sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModel.kt (1)

29-29: Remove unnecessary empty line.

    ) {
-
        dialogState = OpenUrlConfirmDialogState(
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (2)

19-19: Remove empty default constructor.

The empty constructor serves no purpose and can be removed as flagged by static analysis.

-class CustomTemplateViewModel() : ViewModel() {
+class CustomTemplateViewModel : ViewModel() {

32-42: Consider refactoring method with too many parameters.

The showDialog method has 9 parameters, which makes it difficult to use and maintain. Consider using a data class or builder pattern.

data class DialogConfig(
    val title: String,
    val message: String,
    val imageUrl: String? = null,
    val primaryButtonText: String = "Continue",
    val secondaryButtonText: String = "Close",
    val autoCloseAfterSeconds: Int = 0,
    val onPrimaryAction: () -> Unit = {},
    val onSecondaryAction: () -> Unit = {},
    val onAutoClose: () -> Unit = {}
)

fun showDialog(config: DialogConfig) {
    // Implementation using config properties
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 845be48 and fee6f1f.

📒 Files selected for processing (9)
  • sample/src/main/java/com/clevertap/demo/HomeScreenActivity.kt (6 hunks)
  • sample/src/main/java/com/clevertap/demo/MyApplication.kt (7 hunks)
  • sample/src/main/java/com/clevertap/demo/ViewModelFactory.kt (2 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModelProvider.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateDefinitions.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmDialog.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModel.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModelProvider.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • sample/src/main/java/com/clevertap/demo/MyApplication.kt
  • sample/src/main/java/com/clevertap/demo/ViewModelFactory.kt
  • sample/src/main/java/com/clevertap/demo/HomeScreenActivity.kt
🧰 Additional context used
🪛 detekt (1.23.8)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt

[warning] 19-19: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: lint-staticChecks-test-build
🔇 Additional comments (5)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModelProvider.kt (1)

14-16: I’ll inspect the declaration and initialization of ctInstance in MyApplication to confirm if it can ever remain null:

#!/bin/bash
# Show ctInstance declaration and nearby context
rg "ctInstance" -n -C5 sample/src/main/java/com/clevertap/demo/MyApplication.kt

# Show where onCreate sets up ctInstance
rg "override fun onCreate" -n -C5 sample/src/main/java/com/clevertap/demo/MyApplication.kt

# Locate buildCtInstance implementation
rg "fun buildCtInstance" -n -C5 sample/src/main/java/com/clevertap/demo/MyApplication.kt
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModelProvider.kt (1)

14-16: Add null safety check for MyApplication.ctInstance.

Same issue as in OpenUrlConfirmViewModelProvider - the ViewModelFactory initialization could fail if MyApplication.ctInstance is null.

Consider adding the same null check pattern for consistency:

    private val viewModelFactory by lazy {
-        ViewModelFactory(MyApplication.ctInstance)
+        ViewModelFactory(MyApplication.ctInstance ?: throw IllegalStateException("Application instance not initialized"))
    }
sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModel.kt (1)

15-67: Well-structured ViewModel implementation.

The ViewModel follows Android architecture patterns correctly with proper state management using Compose's mutableStateOf, appropriate cleanup in onCleared(), and good separation of concerns.

sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomTemplateDefinitions.kt (2)

1-14: LGTM! Clean imports and package structure.

The imports are well-organized and include all necessary dependencies for the custom template functionality.


15-51: Well-structured template definitions with comprehensive arguments.

The template and function definitions are properly configured with appropriate argument types, default values, and presenter assignments. The visual/non-visual distinction is correctly implemented.

…ple_app

task(SDK-4886) - UI and integration for custom inbox in the sample app
@Anush-Shand Anush-Shand changed the title Task/sdk 4887/sample custom template Task/sdk 4887/sample custom template + app_inbox Jun 19, 2025
Copy link
Contributor

@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)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (1)

92-98: Remove manual viewModelScope cancellation.

The built-in viewModelScope is automatically cancelled when the ViewModel is cleared. Manual cancellation is unnecessary and potentially problematic.

     override fun onCleared() {
         super.onCleared()
         autoCloseJob?.cancel()
-        viewModelScope.cancel()
         forceHideDialog()
     }
🧹 Nitpick comments (2)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (2)

18-18: Remove the empty default constructor.

The empty default constructor is unnecessary and can be removed as indicated by static analysis.

-class CustomTemplateViewModel() : ViewModel() {
+class CustomTemplateViewModel : ViewModel() {

86-90: Consider consolidating duplicate logic.

forceHideDialog appears to duplicate the logic of hideDialog. Consider whether both methods are necessary or if they can be consolidated.

If both methods serve the same purpose, you could consolidate them:

-    fun forceHideDialog() {
-        autoCloseJob?.cancel()
-        dialogState = CustomTemplateDialogState(isVisible = false, config = null)
-    }

Or if forceHideDialog has specific semantics, consider adding documentation to clarify the difference.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 23cc90e and 5c019f9.

📒 Files selected for processing (2)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt
🧰 Additional context used
🪛 detekt (1.23.8)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt

[warning] 18-18: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: lint-staticChecks-test-build
🔇 Additional comments (5)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (5)

1-12: LGTM! Clean imports and package structure.

The imports are well-organized and appropriate for the ViewModel functionality with Compose integration.


13-16: LGTM! Well-structured data class.

The data class properly encapsulates dialog state with appropriate default values.


20-24: LGTM! Proper state management setup.

Good use of mutableStateOf for Compose state management with private setter to encapsulate state mutations.


29-74: LGTM! Well-implemented dialog configuration.

The showDialog method properly handles dialog configuration, auto-close functionality, and coroutine job management. Good practice of canceling existing jobs before starting new ones.


76-84: LGTM! Proper cleanup in hideDialog.

Good job canceling the auto-close job and resetting state when hiding the dialog.

Copy link
Collaborator

Choose a reason for hiding this comment

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

checkFirstTimePreferences method should be in Viewmodel

Copy link
Collaborator

@CTLalit CTLalit left a comment

Choose a reason for hiding this comment

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

Already reviewed by parts.

Copy link
Collaborator

@CTLalit CTLalit left a comment

Choose a reason for hiding this comment

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

Added some comments around anon lambdas.

Copy link
Contributor

@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)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (3)

17-17: Remove the empty default constructor.

The empty default constructor is unnecessary and can be removed as flagged by the static analysis tool.

-class CustomTemplateViewModel() : ViewModel() {
+class CustomTemplateViewModel : ViewModel() {

28-73: Consider refactoring the showDialog method to improve maintainability.

The method has 8 parameters, which makes it complex to use and maintain. Consider using a data class or builder pattern to encapsulate the dialog configuration parameters.

Example refactor using a configuration data class:

data class DialogConfig(
    val title: String,
    val message: String,
    val imageUrl: String? = null,
    val primaryButtonText: String = "Continue",
    val secondaryButtonText: String = "Close",
    val autoCloseAfterSeconds: Int = 0,
    val onPrimaryAction: () -> Unit = {},
    val onSecondaryAction: () -> Unit = {},
    val onAutoClose: () -> Unit = {}
)

fun showDialog(config: DialogConfig) {
    // Implementation using config properties
}

75-88: Consider consolidating hideDialog and forceHideDialog methods.

Both methods perform nearly identical operations - they cancel the auto-close job and reset the dialog state. The duplication could be eliminated by having one method call the other or by extracting the common logic.

Looking at the relevant code from OpenUrlConfirmViewModel.kt, this pattern appears consistent across the codebase, so this refactor should be applied consistently if pursued.

 fun hideDialog() {
-    // Cancel auto-close job when manually hiding dialog
-    autoCloseJob?.cancel()
-    dialogState = CustomTemplateDialogState(
-        isVisible = false,
-        config = null
-    )
+    forceHideDialog()
 }

 fun forceHideDialog() {
+    // Cancel auto-close job when hiding dialog
     autoCloseJob?.cancel()
     dialogState = CustomTemplateDialogState(isVisible = false, config = null)
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5c019f9 and 2d2629f.

📒 Files selected for processing (3)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmDialog.kt (1 hunks)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmDialog.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • sample/src/main/java/com/clevertap/demo/ui/customtemplates/OverlayDialog.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (1)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/OpenUrlConfirmViewModel.kt (2)
  • hideDialog (47-54)
  • forceHideDialog (59-61)
🪛 detekt (1.23.8)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt

[warning] 17-17: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: lint-staticChecks-test-build
🔇 Additional comments (2)
sample/src/main/java/com/clevertap/demo/ui/customtemplates/CustomInterstitialViewModel.kt (2)

19-23: LGTM: Proper state management and coroutine handling.

The use of mutableStateOf for Compose state management and the private setter for encapsulation is well implemented. The auto-close job management follows Android best practices.


91-95: LGTM: Proper ViewModel lifecycle cleanup.

The onCleared() method correctly cancels the coroutine job and cleans up the dialog state, preventing memory leaks and ensuring proper resource management.

@Anush-Shand Anush-Shand merged commit c023b64 into develop Jun 27, 2025
5 checks passed
@Anush-Shand Anush-Shand deleted the task/SDK-4887/sample-custom-template branch June 27, 2025 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants