Skip to content

💄 [KMM] Liquid glass tab bar in compose multiplatform#43

Open
JuliaJakubcova wants to merge 9 commits into
mainfrom
feature/kmm/cmp-liquid-glass
Open

💄 [KMM] Liquid glass tab bar in compose multiplatform#43
JuliaJakubcova wants to merge 9 commits into
mainfrom
feature/kmm/cmp-liquid-glass

Conversation

@JuliaJakubcova

@JuliaJakubcova JuliaJakubcova commented Oct 6, 2025

Copy link
Copy Markdown
Collaborator

📝 Description

  • Expect/actual view for drawing tab bar with tabs, on iOS 26 it's liquid glass, on lower iOS and android it's native look

💡 What’s new?

  • ScreenWithPlatformSpecificBottomBar view

😶 What’s missing?

  • Nothing

Summary by CodeRabbit

  • New Features

    • Cross-platform tabbed sample flow: multi-tab UI with per-tab content, toolbar actions, theming, blurred container, and platform-native scaffold/view-factory hosting.
  • iOS

    • New SwiftUI screens, hosting views, observables/delegates, view factories, improved safe-area handling, DI wiring, and Xcode scheme now invokes the Gradle embed/sign task.
  • Android

    • New composable route and tabbed screen wiring, native scaffold implementation, and image-loading support.
  • Chores

    • Added sampleTabBar module, dependency updates (Compose, Coil, Haze) and build/configuration adjustments.

@coderabbitai

coderabbitai Bot commented Oct 6, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new shared module sampletabbar (UI, ViewModel, theme, DI, Android/iOS actuals), integrates it into builds and settings, introduces SwiftUI hosts/observables on iOS, updates app wiring/DI, adjusts Compose tooling and libs, and switches an Xcode scheme pre-action to run a Gradle embed-and-sign task.

Changes

Cohort / File(s) Summary
Xcode scheme pre-action
ios/MateeStarter.xcodeproj/xcshareddata/xcschemes/MateeStarter_Alpha.xcscheme
Pre-action changed to run Gradle embed/sign task (./gradlew :shared:core:embedAndSignAppleFrameworkForXcode < /dev/null).
Project includes & build exports
settings.gradle.kts, build-logic/convention/src/main/kotlin/config/KmmConfig.kt, shared/core/build.gradle.kts
Adds :shared:sampletabbar to settings, exports it via KMM config, and adds (duplicate) commonMainApi(project(":shared:sampletabbar")).
Version/catalog updates
gradle/libs.versions.toml
Bumps JetBrains Compose plugin to 1.9.0, replaces accompanist-coil with Coil modules, and adds haze artifacts and versions.
New module: sampletabbar — common API & models
shared/sampletabbar/src/commonMain/.../NativeScaffold.kt, .../ComposeViewFactory.kt, .../LocalViewFactory.kt
Adds expect API for NativeScaffold, Toolbar/Tab models, NativeColor expect class, expect ComposeViewFactory, and a composition local LocalViewFactory.
New module: sampletabbar — UI, theme, VM
shared/sampletabbar/src/commonMain/kotlin/.../presentation/ui/SampleTabBarScreen.kt, .../SampleTheme.kt, .../BlurredContainer.kt, .../vm/SampleTabBarViewModel.kt
New SampleTabBarScreen composable, theme, BlurredContainer, ViewModel, state/intent/event types, and SampleTab enum.
sampletabbar Android actuals
shared/sampletabbar/src/androidMain/.../NativeScaffold.android.kt, shared/sampletabbar/src/androidMain/.../ComposeViewFactory.kt
Android actuals for NativeScaffold and ComposeViewFactory (Material3 top app bar + bottom navigation).
sampletabbar iOS actuals & controllers
shared/sampletabbar/src/iosMain/.../NativeScaffold.ios.kt, .../ComposeViewFactory.kt, .../NativeScaffoldDelegate.kt, .../SampleTabBarViewController.kt
iOS actuals implementing NativeScaffold, NativeColor conversion, Compose-backed UIKit controllers, delegate interfaces, and SampleTabBarViewController.
Compose/compose-multiplatform integration
shared/samplecomposemultiplatform/src/commonMain/.../ScreenWithPlatformSpecificBottomBar.kt, .../SampleComposeMultiplatformScreen.kt, shared/samplecomposemultiplatform/build.gradle.kts
Adds expect ScreenWithPlatformSpecificBottomBar, BottomBarTab model, replaces sample screen with multi-tab layout, and adds Material3 dependency.
iOS SwiftUI hosts, observables, factories
ios/PresentationLayer/.../PlatformSpecificView/SwiftUIFactory.swift, .../NativeScaffoldView.swift, .../NativeScaffoldObservable.swift, .../ScreenWithBottomBarView.swift, .../ScreenWithPlatformSpecificBottomBarObservable.swift, .../SwiftUIComposeMultiplatformViewFactory.swift, .../SampleTabBarView.swift, .../TestScreen.swift, .../UIToolkit/Extensions/Color+Extensions.swift
Adds SwiftUIViewFactory, NativeScaffoldView and observables, ScreenWithBottomBarView + observable bridge, view-factory creators, SampleTabBarView SwiftUI wrapper, TestScreen, and Color initializer for KMP color.
iOS host wiring & DI
ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift, ios/Application/MainFlowController.swift
Adds DI factory sampleTabBarViewModel and updates MainFlowController to present SampleTabBarView via UIHostingController with injected VM.
iOS Compose hosting tweak
shared/samplecomposemultiplatform/src/iosMain/.../SampleComposeMultiplatformScreenViewController.kt
Opts into experimental Compose APIs and configures ComposeUIViewController (opaque = false).
iOS layout tweaks
ios/PresentationLayer/.../SampleComposeMultiplatformView.swift
Adds .ignoresSafeArea() on root frame.
Android app route & wiring
android/app/src/main/kotlin/kmp/android/MainActivity.kt
Adds SampleTabBarMainRoute composable and mounts it in Activity content (uses koinViewModel, state collection, LaunchedEffect).
Core wiring (DI modules & tests)
shared/core/src/commonMain/kotlin/kmp/shared/core/di/Module.kt, shared/core/src/androidUnitTest/.../ComposeTest.kt
Adds sampleTabBarModule to Koin modules and updates a unit test to accept actual modifier on functions with "modifier".

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Platform as Host (Android/iOS)
  participant Delegate as Observable/Delegate
  participant KMP as Shared Composable
  participant VM as SampleTabBarViewModel

  User->>Platform: Tap tab
  alt iOS SwiftUI host
    Platform->>Delegate: set selectedTab
    Delegate->>KMP: onSelectedTabChanged(pos)
  else Android host
    Platform->>KMP: onTabSelected(pos)
  end
  KMP->>VM: applyIntent(OnTabSelected(pos))
  VM-->>KMP: updated state
  KMP->>Platform: recomposed content for selected tab
Loading
sequenceDiagram
  autonumber
  participant App
  participant Common as Common expect NativeScaffold
  participant Android as Android actual
  participant iOS as iOS actual
  participant Host as SwiftUIViewFactory

  App->>Common: request NativeScaffold(...)
  Common->>Android: Android actual builds Compose UI
  Common->>iOS: iOS actual builds ComposeUIViewControllers per tab + delegate
  iOS->>Host: returns (UIViewController, NativeScaffoldDelegate)
  Host->>App: UIHostingController presented
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Seidak
  • tomas-bat
  • vojta-horanek

Poem

I hop through tabs and stitch the seams,
Compose and Swift in woven dreams.
I nudge the build, I sign the frame,
New tabs and themes — I know their name.
Hooray — a bridge for code and view 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description provides the required Description, What’s New, and What’s Missing sections but omits the References section mandated by the repository template, leaving out any links to related issues or documentation. Please add the References section from the template and include any relevant issue numbers, design documents, or external links that support this change.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the main change by indicating the addition of a liquid glass tab bar in the Kotlin Multiplatform Compose UI, matching the pull request’s focus on the ScreenWithPlatformSpecificBottomBar component and its iOS‐specific appearance.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/kmm/cmp-liquid-glass

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 and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @JuliaJakubcova, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new, platform-aware bottom navigation bar component to the KMM Compose Multiplatform sample application. The primary goal is to demonstrate how to achieve distinct UI appearances on different platforms and OS versions, specifically a 'liquid glass' effect on modern iOS while maintaining a native look elsewhere. This enhances the application's ability to adapt its user interface to platform-specific design guidelines and features, improving the overall user experience.

Highlights

  • Platform-Specific Tab Bar: Introduces an expect/actual implementation for a bottom tab bar, providing a 'liquid glass' effect on iOS 26+ (via SwiftUI) and a native look on Android and older iOS versions (via Compose Material).
  • New UI Component: Adds ScreenWithPlatformSpecificBottomBar to encapsulate the logic and rendering for the platform-aware tab bar across different targets.
  • iOS Integration: New SwiftUI views (ScreenWithBottomBarView, ScreenWithPlatformSpecificBottomBarObservable) and a factory method are added to seamlessly bridge Compose Multiplatform content into a SwiftUI TabView on iOS.
  • Build System Update: The iOS Xcode scheme is updated to use a Gradle task for embedding and signing the shared KMM framework, streamlining the build process.
  • Sample Screen Refactor: The main sample screen (SampleComposeMultiplatformScreen) is refactored to utilize the new ScreenWithPlatformSpecificBottomBar with multiple tabs (Home, Search, Profile) and their respective content.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a platform-specific tab bar using Compose Multiplatform's expect/actual mechanism, with a native-like implementation for Android and a SwiftUI TabView for iOS, which enables a "liquid glass" effect on newer iOS versions. The implementation is well-structured, using view models and delegates for the iOS-KMP bridge. My feedback focuses on improving code maintainability by reducing duplication in both Kotlin and Swift code, and addresses a potential typo in an iOS version check that could affect the intended UI behavior.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc931b8 and dda6dac.

⛔ Files ignored due to path filters (3)
  • shared/base/src/commonMain/moko-resources/images/home.svg is excluded by !**/*.svg
  • shared/base/src/commonMain/moko-resources/images/person.svg is excluded by !**/*.svg
  • shared/base/src/commonMain/moko-resources/images/search.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • android/samplecomposemultiplatform/src/main/kotlin/kmp/android/samplecomposemultiplatform/ui/SampleComposeMultiplaformMain.kt (2 hunks)
  • ios/MateeStarter.xcodeproj/xcshareddata/xcschemes/MateeStarter_Alpha.xcscheme (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithPlatformSpecificBottomBarObservable.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIComposeMultiplatformViewFactory.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleComposeMultiplatformView.swift (1 hunks)
  • shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/SampleComposeMultiplatformScreen.kt (2 hunks)
  • shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/SampleComposeMultiplatformScreenViewController.kt (2 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ComposeSampleComposeMultiplatformViewFactory.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.ios.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBarDelegate.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIComposeMultiplatformViewFactory.swift (1)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ComposeSampleComposeMultiplatformViewFactory.kt (1)
  • createScreenWithPlatformSpecificBottomBar (13-17)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithPlatformSpecificBottomBarObservable.swift (1)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBarDelegate.kt (4)
  • updateTabs (5-9)
  • updateTabs (6-6)
  • updateOnSelectedTabChanged (8-8)
  • updateSelectedTab (7-7)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (2)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1)
  • title (15-20)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1)
  • title (7-12)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/SampleComposeMultiplatformScreen.kt (7)
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1)
  • ScreenWithPlatformSpecificBottomBar (15-48)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1)
  • ScreenWithPlatformSpecificBottomBar (7-13)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.ios.kt (1)
  • ScreenWithPlatformSpecificBottomBar (18-83)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/PlatformSpecificCheckboxView.kt (1)
  • PlatformSpecificCheckboxView (14-51)
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/PlatformSpecificCheckboxView.kt (1)
  • PlatformSpecificCheckboxView (12-34)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/PlatformSpecificCheckboxView.kt (1)
  • PlatformSpecificCheckboxView (6-12)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/common/StarterButton.kt (1)
  • StarterButton (17-42)
🪛 SwiftLint (0.57.0)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithPlatformSpecificBottomBarObservable.swift

[Warning] 9-9: Type name 'ScreenWithPlatformSpecificBottomBarObservable' should be between 3 and 40 characters long

(type_name)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run tests
  • GitHub Check: Lint & Build
🔇 Additional comments (7)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIComposeMultiplatformViewFactory.swift (1)

24-39: LGTM!

The factory method correctly bridges the Swift and Kotlin layers, wrapping the callback to convert Swift Int to KotlinInt and returning the view controller with its observable delegate.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/SampleComposeMultiplatformScreenViewController.kt (1)

29-33: LGTM! Transparency configuration is appropriate.

Setting opaque = false is correct for enabling transparency in the Compose view controller, which is necessary for the platform-specific bottom bar integration.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (1)

10-38: LGTM! Well-structured platform-specific bottom bar.

The implementation correctly:

  • Uses iOS 26 availability check to conditionally apply the "liquid glass" safe area behavior
  • Provides graceful fallback for earlier iOS versions
  • Binds the selected tab to the observable's state
  • Renders tab items with icons and titles
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1)

15-48: LGTM! Android implementation is correct.

The Android actual function correctly implements the bottom navigation pattern with proper tab selection and content rendering.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1)

7-12: LGTM! iOS-specific tab model is appropriate.

The data class correctly models an iOS tab with a UIViewController for content, complementing the common BottomBarTab that uses Composable content.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ComposeSampleComposeMultiplatformViewFactory.kt (1)

13-17: LGTM! Factory interface is well-defined.

The new factory method signature correctly defines the contract for creating a platform-specific bottom bar with its delegate, matching the Swift implementation.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBarDelegate.kt (1)

5-9: LGTM! Delegate interface is well-structured.

The interface correctly defines the contract for updating bottom bar state from the Kotlin side, with methods for updating tabs, the selected tab, and the selection callback.

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

♻️ Duplicate comments (3)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/SampleComposeMultiplatformScreenViewController.kt (1)

23-23: This comment duplicates a previous review comment that asked for documentation explaining why the experimental APIs are necessary.

shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/SampleComposeMultiplatformScreen.kt (1)

48-91: This comment duplicates a previous review comment that flagged a critical issue: tabs are recreated on every recomposition, causing iOS view controllers to rebuild and producing visible flicker. The previous comment provided a detailed solution using remember and rememberUpdatedState.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.ios.kt (1)

29-43: Reduce code duplication in tab mapping.

The mapping tabs.map { it.toIosTab(factory) } appears twice: once when creating the view model (line 32) and again when updating tabs (line 41). This violates DRY principles.

Consider extracting the mapped tabs into a remembered value:

     val factory = LocalSampleComposeMultiplatformViewFactory.current
     val key = rememberSaveable { Random.nextInt().toString(16) }
+    val iosTabs = remember(tabs, factory) { tabs.map { it.toIosTab(factory) } }
 
     val viewModel = viewModel(key = key) {
         NativeViewHolderViewModel {
             factory.createScreenWithPlatformSpecificBottomBar(
-                tabs = tabs.map { it.toIosTab(factory) },
+                tabs = iosTabs,
                 selectedTabPosition = selectedTabPosition,
                 onSelectedTabChanged = onSelectedTabChanged,
             )
         }
     }
     val delegate = remember(viewModel) { viewModel.delegate }
     val view = remember(viewModel) { viewModel.view }
 
-    LaunchedEffect(tabs) { delegate.updateTabs(tabs.map { it.toIosTab(factory) }) }
+    LaunchedEffect(iosTabs) { delegate.updateTabs(iosTabs) }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dda6dac and d3b2a1e.

📒 Files selected for processing (6)
  • shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/SampleComposeMultiplatformScreen.kt (2 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/SampleComposeMultiplatformScreenViewController.kt (2 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.ios.kt (1 hunks)
  • shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBarDelegate.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/SampleComposeMultiplatformScreen.kt (7)
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1)
  • ScreenWithPlatformSpecificBottomBar (14-47)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.ios.kt (1)
  • ScreenWithPlatformSpecificBottomBar (18-53)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1)
  • ScreenWithPlatformSpecificBottomBar (7-13)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/PlatformSpecificCheckboxView.kt (1)
  • PlatformSpecificCheckboxView (14-51)
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/PlatformSpecificCheckboxView.kt (1)
  • PlatformSpecificCheckboxView (12-34)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/PlatformSpecificCheckboxView.kt (1)
  • PlatformSpecificCheckboxView (6-12)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/common/StarterButton.kt (1)
  • StarterButton (17-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run tests
  • GitHub Check: Lint & Build
🔇 Additional comments (5)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/SampleComposeMultiplatformScreenViewController.kt (1)

29-33: LGTM! Transparency configuration enables liquid glass effect.

Setting opaque = false is correct for achieving the liquid glass tab bar appearance on iOS 26+. The configure block properly initializes the view controller with transparency support.

shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1)

14-47: LGTM! Clean Android bottom bar implementation.

The Android actual implementation correctly:

  • Uses a weighted Column layout for proper space distribution
  • Selects and renders the active tab content
  • Provides standard Material BottomNavigation with icons and labels
  • Handles tab selection via the callback
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1)

6-11: LGTM! Appropriate iOS tab model.

The data class correctly represents an iOS tab with all necessary properties. Using UIViewController for content aligns with iOS platform requirements.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBarDelegate.kt (1)

3-7: LGTM! Well-defined delegate interface.

The interface provides a clean contract for iOS integration, covering all necessary update methods for tabs, selection state, and callbacks.

shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.ios.kt (1)

55-67: LGTM! Well-structured helper function.

The toIosTab extension function provides a clean conversion between common and iOS-specific tab representations, properly wrapping Compose content in a UIViewController with the necessary composition locals.

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

Caution

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

⚠️ Outside diff range comments (1)
android/app/src/main/kotlin/kmp/android/MainActivity.kt (1)

18-33: Move setContent to onCreate (don’t recreate composition in onStart)

Calling setContent in onStart resets the composition on every start/resume, losing state and wasting work. Initialize once in onCreate.

 class MainActivity : BaseActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         WindowCompat.setDecorFitsSystemWindows(window, false)
-    }
-
-    override fun onStart() {
-        super.onStart()
         setContent {
             SampleTheme {
-//                Root()
                 SampleTabBarMainRoute()
             }
         }
     }
-}
+}
-
-@Composable
+@Composable
 internal fun SampleTabBarMainRoute(
♻️ Duplicate comments (1)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (1)

23-28: Verify the iOS version check.

A previous review flagged that iOS 26.0 is a future version and likely incorrect. The "liquid glass" tab bar appearance was introduced in earlier iOS versions. Please verify the correct iOS version for this feature.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3b2a1e and b1b00f5.

📒 Files selected for processing (32)
  • android/app/src/main/kotlin/kmp/android/MainActivity.kt (2 hunks)
  • build-logic/convention/src/main/kotlin/config/KmmConfig.kt (1 hunks)
  • gradle/libs.versions.toml (4 hunks)
  • ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift (1 hunks)
  • ios/Application/MainFlowController.swift (2 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (1 hunks)
  • ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1 hunks)
  • settings.gradle.kts (1 hunks)
  • shared/core/build.gradle.kts (1 hunks)
  • shared/core/src/commonMain/kotlin/kmp/shared/core/di/Module.kt (2 hunks)
  • shared/samplecomposemultiplatform/build.gradle.kts (1 hunks)
  • shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1 hunks)
  • shared/sampletabbar/build.gradle.kts (1 hunks)
  • shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/di/Module.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/LocalViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (2)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)
  • SampleTabBarViewController (17-47)
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/View+Extensions.swift (1)
  • toastView (36-38)
shared/sampletabbar/build.gradle.kts (1)
build-logic/convention/src/main/kotlin/extensions/DependencyExtensions.kt (1)
  • ktlintRuleset (35-36)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)
  • tabs (29-32)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)
  • icon (25-31)
  • title (20-23)
  • title (42-46)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (2)
  • createNativeScaffold (6-15)
  • createNativeScaffold (8-14)
android/app/src/main/kotlin/kmp/android/MainActivity.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1)
  • SampleTabBarScreen (32-110)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1)
  • SampleTabBarScreen (32-110)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)
  • tabs (29-32)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (4)
  • updateToolbar (3-7)
  • updateToolbar (4-4)
  • updateTabs (5-5)
  • updateSelectedTab (6-6)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)
  • BlurredContainer (27-89)
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)
  • toUIColor (82-88)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (2)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (2)
  • index (34-37)
  • index (36-36)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (4)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)
  • icon (25-31)
  • title (20-23)
  • title (42-46)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1)
  • title (6-11)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1)
  • title (15-20)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (3)
shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1)
  • NativeScaffold (26-114)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (1)
  • NativeScaffold (10-18)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)
  • NativeScaffold (25-78)
🪛 detekt (1.23.8)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt

[warning] 1-48: The file does not contain a package declaration.

(detekt.potential-bugs.MissingPackageDeclaration)

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt

[warning] 1-150: The file does not contain a package declaration.

(detekt.potential-bugs.MissingPackageDeclaration)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests
🔇 Additional comments (25)
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1)

18-29: The actual implementation structure is sound.

The function correctly implements the expected Android bottom-bar API. The Column layout with weighted content Box and bottom NavigationBar is appropriate. The safe tab lookup with firstOrNull handles missing tabs gracefully by rendering an empty content area.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (3)

1-12: LGTM!

The struct declaration, state management, and public initializer follow standard SwiftUI patterns.


37-56: Verify if button actions should be implemented.

All toolbar button actions (lines 40, 47, 53) are currently empty. Confirm whether these are intentional placeholders for a test screen or if they should be wired up to functionality (e.g., connected to the SampleTabBarViewModel mentioned in the PR).


58-60: Verify iOS version support requirements.

The fallback implementation for iOS versions below 16 is missing. Confirm whether the app's minimum deployment target is iOS 16+ or if a fallback implementation is needed.

If iOS < 16 support is required, consider providing a basic TabView implementation without NavigationStack, or display an appropriate message to users.

shared/samplecomposemultiplatform/build.gradle.kts (1)

27-27: LGTM!

The Material 3 dependency addition aligns with the new theme and UI components introduced in the sampletabbar module.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (1)

17-21: LGTM!

The didSet guard prevents unnecessary callback invocations, and the update methods correctly propagate state changes to the observable properties.

Also applies to: 37-47

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (2)

15-86: LGTM!

The color schemes are well-defined with proper light/dark mode support, and the shapes configuration follows Material 3 conventions. The empty typography placeholder is acceptable for initial implementation.


88-97: LGTM!

The theme composable correctly selects the color scheme based on system dark theme and applies all Material 3 configurations.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)

35-40: LGTM!

The haze state setup and gradient implementations correctly use theme-aware colors and properly structure the blur effects.

Also applies to: 70-87

build-logic/convention/src/main/kotlin/config/KmmConfig.kt (1)

62-62: LGTM!

The sampletabbar module export is correctly added to the iOS framework configuration, aligning with the new cross-platform tab bar feature.

settings.gradle.kts (1)

34-34: LGTM!

The sampletabbar module inclusion is correctly added to the project settings.

shared/core/src/commonMain/kotlin/kmp/shared/core/di/Module.kt (1)

10-10: LGTM!

The sampleTabBarModule is correctly imported and wired into the Koin dependency injection setup.

Also applies to: 26-26

shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

1-4: LGTM!

The empty marker interface serves as the Android-specific actual implementation, which is appropriate for this platform where no additional factory methods are needed.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/di/Module.kt (1)

1-9: LGTM!

The Koin module is correctly defined with a standard viewModelOf binding for SampleTabBarViewModel.

ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1)

10-14: LGTM!

The Color extension provides a clean bridge between Kotlin Multiplatform's NativeColor and SwiftUI's Color type.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

1-7: LGTM!

The expect/actual pattern is correctly implemented for the cross-platform ComposeViewFactory interface.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/LocalViewFactory.kt (1)

7-14: LGTM!

The CompositionLocal is correctly configured with an appropriate error message for missing provider.

ios/Application/MainFlowController.swift (1)

28-29: LGTM!

The new SwiftUI-based tab bar implementation correctly uses dependency injection and follows best practices.

ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift (1)

17-17: LGTM!

The DI property for SampleTabBarViewModel follows the established pattern and is correctly implemented.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (1)

3-7: LGTM

Interface is minimal and clear, matches expected responsibilities.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

6-15: LGTM

Actual interface matches the platform contract and pairs nicely with the Swift implementation.

shared/sampletabbar/build.gradle.kts (1)

19-39: Ignore custom source-set verification

commonMainImplementation, androidMainImplementation, and iosMainImplementation are automatically registered by the Kotlin Multiplatform plugin; no custom build-logic is required.

Likely an incorrect or invalid review comment.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)

22-46: iOS Compose wiring looks solid.

ViewModel retrieval, lifecycle state collection, one‑shot OnAppeared, and event forwarding are all correct.

shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1)

100-105: Revisit bottom insets application for NavigationBar.

Padding each NavigationBarItem with systemBars bottom may produce uneven touch targets. Prefer applying insets at the NavigationBar level (e.g., windowInsetsPadding with bottom-only) and remove per-item padding.

Would you verify on devices with gesture navigation? If needed, I can propose a patch to move padding to the bar container.

gradle/libs.versions.toml (1)

19-19: Compatibility and cleanup verified—no action needed

org.jetbrains.compose 1.9.0 is compatible with Kotlin 2.2.10 and compose-bom 2025.08.01 (the BOM covers libraries only; the plugin manages the Compose compiler). Ripgrep found no accompanist-coil references.

Comment on lines +30 to +33
// return UIHostingController(rootView: TestScreen())
// let main = UITabBarController()
// main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()]
// return main

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Remove commented-out code.

The commented alternative implementations should be removed to maintain code cleanliness. Version control preserves the history if needed.

Apply this diff:

-//        return UIHostingController(rootView: TestScreen())
-//        let main = UITabBarController()
-//        main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()]
-//        return main
📝 Committable suggestion

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

Suggested change
// return UIHostingController(rootView: TestScreen())
// let main = UITabBarController()
// main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()]
// return main
🤖 Prompt for AI Agents
In ios/Application/MainFlowController.swift around lines 30 to 33, remove the
commented-out alternative implementations (the UIHostingController return and
the sample UITabBarController setup) so the file contains only the active
implementation; commit the cleaned file with those comment lines deleted to keep
codebase tidy (history retained in VCS).

Comment on lines +24 to +35
init(
toolbar: Toolbar?,
tabs: [TabItem],
selectedTab: Int32,
onSelectedTabChanged: @escaping (Int32) -> Void,
content: @escaping (Int32?) -> UIViewController
) {
self.tabs = tabs
self.selectedTab = selectedTab
self.onSelectedTabChanged = onSelectedTabChanged
self.content = content
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing toolbar initialization.

The toolbar property is never assigned in the initializer, which will leave it with an uninitialized value. This is a critical bug that will cause undefined behavior.

Apply this diff to fix the initialization:

     init(
         toolbar: Toolbar?,
         tabs: [TabItem],
         selectedTab: Int32,
         onSelectedTabChanged: @escaping (Int32) -> Void,
         content: @escaping (Int32?) -> UIViewController
     ) {
+        self.toolbar = toolbar
         self.tabs = tabs
         self.selectedTab = selectedTab
         self.onSelectedTabChanged = onSelectedTabChanged
         self.content = content
     }
📝 Committable suggestion

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

Suggested change
init(
toolbar: Toolbar?,
tabs: [TabItem],
selectedTab: Int32,
onSelectedTabChanged: @escaping (Int32) -> Void,
content: @escaping (Int32?) -> UIViewController
) {
self.tabs = tabs
self.selectedTab = selectedTab
self.onSelectedTabChanged = onSelectedTabChanged
self.content = content
}
init(
toolbar: Toolbar?,
tabs: [TabItem],
selectedTab: Int32,
onSelectedTabChanged: @escaping (Int32) -> Void,
content: @escaping (Int32?) -> UIViewController
) {
self.toolbar = toolbar
self.tabs = tabs
self.selectedTab = selectedTab
self.onSelectedTabChanged = onSelectedTabChanged
self.content = content
}
🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift
around lines 24 to 35 the initializer never assigns the toolbar parameter to the
instance property; add an assignment self.toolbar = toolbar inside the init so
the toolbar property is properly initialized (ensure the property exists and
that you use self.toolbar = toolbar to avoid shadowing issues).

Comment on lines +25 to +31
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(tab.position) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(tab.position) }
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix availability check: iOS 26.0 will never be true

The .ignoresSafeArea() branch won’t execute on real devices. Apply it unconditionally (or gate at iOS 14+ where it’s available).

-                                    if #available(iOS 26.0, *) {
-                                        ComposeViewController { observable.content(tab.position) }
-                                            .ignoresSafeArea()
-                                    } else {
-                                        ComposeViewController { observable.content(tab.position) }
-                                    }
+                                    ComposeViewController { observable.content(tab.position) }
+                                        .ignoresSafeArea()
-                        if #available(iOS 26.0, *) {
-                            ComposeViewController { observable.content(nil) }
-                                .ignoresSafeArea()
-                        } else {
-                            ComposeViewController { observable.content(nil) }
-                        }
+                        ComposeViewController { observable.content(nil) }
+                            .ignoresSafeArea()

Also applies to: 42-47

🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift
around lines 25-31 and 42-47, the availability check uses iOS 26.0 which is
never true so .ignoresSafeArea() never runs; remove or relax the availability
guard and call .ignoresSafeArea() unconditionally (or guard at iOS 14+ if you
prefer a minimum availability), updating both occurrences so
ComposeViewController always applies .ignoresSafeArea().

Comment on lines +30 to +36
// ScrollView {
// ForEach((0...20), id: \.self) { _ in
// Text("doiaw jmo dcposekoif eswop[ijj fe ss")
// Color.red
// .frame(height: 100)
// }
// }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Remove commented-out code.

The commented ScrollView block should be removed to maintain code cleanliness. If this code is needed for reference, consider moving it to documentation or version control history.

Apply this diff to remove the commented code:

-                            //                            ScrollView {
-                            //                                ForEach((0...20), id: \.self) { _ in
-                            //                                    Text("doiaw jmo dcposekoif eswop[ijj fe ss")
-                            //                                    Color.red
-                            //                                        .frame(height: 100)
-                            //                                }
-                            //                            }
📝 Committable suggestion

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

Suggested change
// ScrollView {
// ForEach((0...20), id: \.self) { _ in
// Text("doiaw jmo dcposekoif eswop[ijj fe ss")
// Color.red
// .frame(height: 100)
// }
// }
🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift
around lines 30 to 36, remove the commented-out ScrollView block (the
ForEach/Text/Color snippet) to clean up dead code; simply delete those commented
lines from the file so only active code remains, relying on VCS history if the
snippet is needed later.

style = MaterialTheme.typography.headlineMedium,
)
Text(
text = "And this is it's body",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo: “it's” → “its”.

-                        text = "And this is it's body",
+                        text = "And this is its body",
📝 Committable suggestion

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

Suggested change
text = "And this is it's body",
text = "And this is its body",
🤖 Prompt for AI Agents
In
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt
around line 94, the string contains a possessive typo "it's" which should be the
possessive "its"; update the text value from "And this is it's body" to "And
this is its body" to correct the grammar.

Comment on lines +11 to +15
override suspend fun applyIntent(intent: SampleTabBarIntent) {
when (intent) {
SampleTabBarIntent.OnAppeared -> onAppeared()
is SampleTabBarIntent.OnTabSelected -> update { copy(selectedTabPosition = intent.index) }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard selectedTabPosition against out‑of‑range indices.

Prevents OOB on defensive paths.

-            is SampleTabBarIntent.OnTabSelected -> update { copy(selectedTabPosition = intent.index) }
+            is SampleTabBarIntent.OnTabSelected -> update {
+                val max = tabs.lastIndex
+                val safeIndex = if (max >= 0) intent.index.coerceIn(0, max) else 0
+                copy(selectedTabPosition = safeIndex)
+            }
📝 Committable suggestion

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

Suggested change
override suspend fun applyIntent(intent: SampleTabBarIntent) {
when (intent) {
SampleTabBarIntent.OnAppeared -> onAppeared()
is SampleTabBarIntent.OnTabSelected -> update { copy(selectedTabPosition = intent.index) }
}
override suspend fun applyIntent(intent: SampleTabBarIntent) {
when (intent) {
SampleTabBarIntent.OnAppeared -> onAppeared()
is SampleTabBarIntent.OnTabSelected -> update {
val max = tabs.lastIndex
val safeIndex = if (max >= 0) intent.index.coerceIn(0, max) else 0
copy(selectedTabPosition = safeIndex)
}
}
}

@@ -0,0 +1,47 @@
import androidx.compose.runtime.CompositionLocalProvider

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add missing package declaration (detekt).

Align with module path.

+package kmp.shared.sampletabbar.presentation
+
 import androidx.compose.runtime.CompositionLocalProvider
📝 Committable suggestion

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

Suggested change
import androidx.compose.runtime.CompositionLocalProvider
package kmp.shared.sampletabbar.presentation
import androidx.compose.runtime.CompositionLocalProvider
🧰 Tools
🪛 detekt (1.23.8)

[warning] 1-48: The file does not contain a package declaration.

(detekt.potential-bugs.MissingPackageDeclaration)

🤖 Prompt for AI Agents
In
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt
around line 1, the Kotlin file is missing its package declaration which detekt
requires; add a package declaration that matches the module path
(kmp.shared.sampletabbar.presentation) as the first non-comment line of the file
so the file's package aligns with its directory structure.

Comment on lines +41 to +47
BlurredContainer(
top = WindowInsets.safeContent.asPaddingValues().calculateTopPadding(),
bottom = WindowInsets.safeContent.asPaddingValues().calculateBottomPadding(),
modifier = Modifier.fillMaxSize(),
) {
content(position, WindowInsets.safeContent.asPaddingValues())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid repeated WindowInsets computation

Compute safePadding once for readability and minor overhead reduction.

-                BlurredContainer(
-                    top = WindowInsets.safeContent.asPaddingValues().calculateTopPadding(),
-                    bottom = WindowInsets.safeContent.asPaddingValues().calculateBottomPadding(),
-                    modifier = Modifier.fillMaxSize(),
-                ) {
-                    content(position, WindowInsets.safeContent.asPaddingValues())
-                }
+                val safePadding = WindowInsets.safeContent.asPaddingValues()
+                BlurredContainer(
+                    top = safePadding.calculateTopPadding(),
+                    bottom = safePadding.calculateBottomPadding(),
+                    modifier = Modifier.fillMaxSize(),
+                ) {
+                    content(position, safePadding)
+                }
🤖 Prompt for AI Agents
In
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt
around lines 41 to 47, avoid recomputing
WindowInsets.safeContent.asPaddingValues() multiple times: create a local val
safePadding = WindowInsets.safeContent.asPaddingValues() and use
safePadding.calculateTopPadding() and safePadding.calculateBottomPadding() for
the BlurredContainer parameters and pass safePadding to the content(...) call so
the inset values are computed once and reused.

Comment on lines +65 to +67
remember(toolbar) { delegate.updateToolbar(toolbar) }
remember(tabs) { delegate.updateTabs(tabs) }
remember(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use LaunchedEffect for side-effects (don’t use remember)

Triggering delegate updates via remember is an anti-pattern. Use LaunchedEffect keyed by the inputs.

-    remember(toolbar) { delegate.updateToolbar(toolbar) }
-    remember(tabs) { delegate.updateTabs(tabs) }
-    remember(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) }
+    androidx.compose.runtime.LaunchedEffect(toolbar) { delegate.updateToolbar(toolbar) }
+    androidx.compose.runtime.LaunchedEffect(tabs) { delegate.updateTabs(tabs) }
+    androidx.compose.runtime.LaunchedEffect(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) }
🤖 Prompt for AI Agents
In
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt
around lines 65–67, the code uses remember to run side-effects
(delegate.updateToolbar/updateTabs/updateSelectedTab); replace each remember
usage with LaunchedEffect keyed to the corresponding input (toolbar, tabs,
selectedTabPosition) and call the delegate update inside the LaunchedEffect
block so the update runs as a proper composable side-effect tied to the input
changes.

@JuliaJakubcova JuliaJakubcova force-pushed the feature/kmm/cmp-liquid-glass branch from b1b00f5 to 16ede13 Compare October 8, 2025 12:06

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

♻️ Duplicate comments (27)
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1)

47-49: Per-item padding still incorrectly applied.

This issue was previously flagged: applying bottom padding to each NavigationBarItem makes items individually taller rather than offsetting the entire bar. Material3's NavigationBar typically handles window insets automatically. Apply windowInsetsPadding(WindowInsets.systemBars) to the NavigationBar container (lines 31-32) instead.

Refer to the previous review comment for the complete fix.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (2)

23-23: Fix the grammatical error.

The text uses "it's" (contraction of "it is") but should use "its" (possessive) to indicate the item's body.

Apply this diff to fix the typo:

-                                Text("And this is it's body")
+                                Text("And this is its body")

51-55: Remove the duplicate toolbar item.

Lines 51-55 are an exact duplicate of lines 45-49, creating two identical "Second" buttons in the trailing position. This appears to be a copy-paste error.

Apply this diff to remove the duplicate:

                    ToolbarItemGroup(placement: .topBarTrailing) {
                        Button("Second", systemImage: "person") {
                            
                        }
                    }
-                    
-                    ToolbarItemGroup(placement: .topBarTrailing) {
-                        Button("Second", systemImage: "person") {
-                            
-                        }
-                    }
                }
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (3)

30-36: Remove commented-out code.

This commented-out ScrollView block should be removed to maintain code cleanliness. This concern was previously raised but the code is still present.

Apply this diff to remove the commented code:

-                            //                            ScrollView {
-                            //                                ForEach((0...20), id: \.self) { _ in
-                            //                                    Text("doiaw jmo dcposekoif eswop[ijj fe ss")
-                            //                                    Color.red
-                            //                                        .frame(height: 100)
-                            //                                }
-                            //                            }

49-49: Remove commented-out code.

This duplicate commented line should be removed as Line 50 already sets the navigation bar title display mode. This concern was previously raised but the code is still present.

Apply this diff:

-//                .toolbarTitleDisplayMode(.inline)

68-70: Implement fallback for earlier iOS versions.

The fallback block for iOS versions below 16.0 is empty, which means users on those versions will see nothing. This concern was previously raised but remains unaddressed. Either implement a fallback UI using pre-iOS-16 APIs or explicitly document and enforce a minimum iOS 16.0 requirement in the project configuration.

ios/Application/MainFlowController.swift (1)

30-33: Remove commented-out code.

The commented alternative implementations should be removed to maintain code cleanliness. Version control preserves the history if needed.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1)

13-19: Remove or use flowController.

flowController is stored but never used. Either integrate it (navigation) or drop it from the API to avoid dead params.

shared/core/build.gradle.kts (1)

42-43: Remove duplicate dependency declaration.

The sampletabbar dependency is declared twice. Remove the duplicate entry.

shared/sampletabbar/build.gradle.kts (1)

29-31: Avoid shipping tooling deps in release builds.

compose.components.uiToolingPreview is typically debug-only. Consider moving it to a debug configuration to keep release artifacts lean.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)

9-12: Mark factory final (duplicate from prior review).

No subclassing expected; final tightens API and improves dispatch.

-public class SwiftUIViewFactory: ComposeViewFactory {
+public final class SwiftUIViewFactory: ComposeViewFactory {
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)

78-80: Fix typo in comment (duplicate).

"typohraphy" → "typography".

 val typography = Typography(
-    // Define typohraphy
+    // Define typography
 )
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (2)

48-58: Avoid recomputing safe insets; compute once and reuse.

Reduces overhead and clarifies intent.

-                if (showBlur) {
-                    BlurredContainer(
-                        top = WindowInsets.safeContent.asPaddingValues().calculateTopPadding(),
-                        bottom = WindowInsets.safeContent.asPaddingValues()
-                            .calculateBottomPadding(),
-                        modifier = Modifier.fillMaxSize(),
-                    ) {
-                        content(position, WindowInsets.safeContent.asPaddingValues())
-                    }
-                } else {
-                    content(position, WindowInsets.safeContent.asPaddingValues())
-                }
+                val safePadding = WindowInsets.safeContent.asPaddingValues()
+                if (showBlur) {
+                    BlurredContainer(
+                        top = safePadding.calculateTopPadding(),
+                        bottom = safePadding.calculateBottomPadding(),
+                        modifier = Modifier.fillMaxSize(),
+                    ) {
+                        content(position, safePadding)
+                    }
+                } else {
+                    content(position, safePadding)
+                }

76-78: Use LaunchedEffect for side-effects, not remember (duplicate).

Trigger delegate updates as proper side-effects.

-    remember(toolbar) { delegate.updateToolbar(toolbar) }
-    remember(tabs) { delegate.updateTabs(tabs) }
-    remember(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) }
+    androidx.compose.runtime.LaunchedEffect(toolbar) { delegate.updateToolbar(toolbar) }
+    androidx.compose.runtime.LaunchedEffect(tabs) { delegate.updateTabs(tabs) }
+    androidx.compose.runtime.LaunchedEffect(selectedTabPosition) {
+        delegate.updateSelectedTab(selectedTabPosition)
+    }
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)

25-31: Fix availability: .ignoresSafeArea() should not be gated by iOS 26.0 (duplicate).

The condition is never true; apply .ignoresSafeArea() unconditionally.

-                                    if #available(iOS 26.0, *) {
-                                        ComposeViewController { observable.content(tab.position) }
-                                            .ignoresSafeArea()
-                                    } else {
-                                        ComposeViewController { observable.content(tab.position) }
-                                    }
+                                    ComposeViewController { observable.content(tab.position) }
+                                        .ignoresSafeArea()
-                        if #available(iOS 26.0, *) {
-                            ComposeViewController { observable.content(nil) }
-                                .ignoresSafeArea()
-                        } else {
-                            ComposeViewController { observable.content(nil) }
-                        }
+                        ComposeViewController { observable.content(nil) }
+                            .ignoresSafeArea()

Also applies to: 42-47

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (1)

24-35: Initialize toolbar in init (duplicate, critical).

Property is never assigned; leaves stale/nil unexpectedly.

     init(
         toolbar: Toolbar?,
         tabs: [TabItem],
         selectedTab: Int32,
         onSelectedTabChanged: @escaping (Int32) -> Void,
         content: @escaping (Int32?) -> UIViewController
     ) {
+        self.toolbar = toolbar
         self.tabs = tabs
         self.selectedTab = selectedTab
         self.onSelectedTabChanged = onSelectedTabChanged
         self.content = content
     }
shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1)

36-38: Hook up nested scroll for top app bar scrollBehavior.

The scrollBehavior is created but never connected to the Scaffold. Without the nestedScroll modifier, the top app bar won't respond to scroll events from the content.

Apply this diff to connect the scroll behavior:

     val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
     Scaffold(
-        modifier = modifier,
+        modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),

You'll also need to add the import:

 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)

1-1: Add missing package declaration.

The file is missing its package declaration, which is required by detekt and Kotlin conventions.

Apply this diff:

+package kmp.shared.sampletabbar.presentation
+
 import androidx.compose.runtime.CompositionLocalProvider
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)

51-51: Use theme color instead of hardcoded white.

Line 51 uses Color.White as the container color, which doesn't respect the Material theme and will look incorrect in dark mode. Line 43 correctly uses MaterialTheme.colorScheme.background for theming.

Apply this diff:

                             hazeState,
-                            style = HazeMaterials.ultraThin(containerColor = Color.White),
+                            style = HazeMaterials.ultraThin(containerColor = MaterialTheme.colorScheme.surface),
                         ) {
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (4)

1-1: Add missing package declaration.

The file is missing its package declaration, which is required by detekt and Kotlin conventions.

Apply this diff:

+package kmp.shared.sampletabbar.presentation.ui
+
 import androidx.compose.foundation.layout.Column

19-19: Remove unused Haze opt-in and import.

The ExperimentalHazeMaterialsApi import and opt-in annotations are unused in this file. The haze materials are used in platform-specific implementations, not here.

Apply this diff:

 import coil3.compose.AsyncImage
-import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
 import dev.icerock.moko.resources.ImageResource

And update the opt-in annotations:

-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun SampleTabBarScreen(
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun ScrollableContent(

41-41: Make toolbar title resilient to invalid index.

Direct array access with state.selectedTabPosition can crash if the index is out of range. This could happen during state initialization or if the state becomes inconsistent.

Apply this diff:

-            title = SampleTab.entries[state.selectedTabPosition].title,
+            title = state.tabs.getOrNull(state.selectedTabPosition)?.title,

94-94: Fix typo: "it's" → "its".

The possessive form should be "its" not "it's".

Apply this diff:

-                        text = "And this is it's body",
+                        text = "And this is its body",
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)

14-14: Guard selectedTabPosition against out-of-range indices.

The OnTabSelected intent directly updates selectedTabPosition with the provided index without validation. If an invalid index is passed, this will cause crashes in UI code that directly accesses SampleTab.entries[state.selectedTabPosition].

Apply this diff:

-            is SampleTabBarIntent.OnTabSelected -> update { copy(selectedTabPosition = intent.index) }
+            is SampleTabBarIntent.OnTabSelected -> update {
+                val max = tabs.lastIndex
+                val safeIndex = if (max >= 0) intent.index.coerceIn(0, max) else 0
+                copy(selectedTabPosition = safeIndex)
+            }
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)

10-18: Document nullability of currentTabPosition parameter.

The content lambda receives currentTabPosition: Int? as nullable, but the reason for this nullability isn't documented. From the implementations, when tabs.isEmpty(), currentTabPosition will be null.

Add KDoc above the function:

+/**
+ * Platform-specific scaffold composable with optional toolbar and tabs.
+ *
+ * @param currentTabPosition In the content lambda, this will be null when tabs.isEmpty(),
+ *                          otherwise it contains the selected tab index.
+ */
 @Composable
 expect fun NativeScaffold(

25-31: Avoid function properties in data classes.

ToolbarButtonData contains an onClick lambda, which breaks value equality semantics. Lambdas use referential equality, so two instances with identical data but different lambda instances won't be equal.

Apply this diff to convert to a regular class:

-data class ToolbarButtonData(
+class ToolbarButtonData(
     val icon: ImageResource,
     val description: String? = null,
     val position: ToolbarButtonPosition = ToolbarButtonPosition.Trailing,
     val tint: NativeColor? = null,
     val onClick: () -> Unit,
 )

42-46: Remove redundant position field from TabItem.

Since tabs is passed as a List<TabItem>, the position is already implicitly defined by the list index. Maintaining an explicit position field creates a risk of inconsistency if the list is reordered.

Apply this diff:

 data class TabItem(
     val title: String,
     val icon: ImageResource,
-    val position: Int,
 )

Then update usage sites to derive position from the list index (e.g., use tabs.indexOf(tab) or forEachIndexed).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1b00f5 and 16ede13.

📒 Files selected for processing (32)
  • android/app/src/main/kotlin/kmp/android/MainActivity.kt (2 hunks)
  • build-logic/convention/src/main/kotlin/config/KmmConfig.kt (1 hunks)
  • gradle/libs.versions.toml (4 hunks)
  • ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift (1 hunks)
  • ios/Application/MainFlowController.swift (2 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (1 hunks)
  • ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1 hunks)
  • settings.gradle.kts (1 hunks)
  • shared/core/build.gradle.kts (1 hunks)
  • shared/core/src/commonMain/kotlin/kmp/shared/core/di/Module.kt (2 hunks)
  • shared/samplecomposemultiplatform/build.gradle.kts (1 hunks)
  • shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1 hunks)
  • shared/sampletabbar/build.gradle.kts (1 hunks)
  • shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/di/Module.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/LocalViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)
  • BlurredContainer (27-89)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (1)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)
  • icon (25-31)
  • title (20-23)
  • title (42-46)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (3)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)
  • SampleTabBarViewController (17-47)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/NativeViewHolderViewModel.kt (1)
  • factory (6-14)
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/View+Extensions.swift (1)
  • toastView (36-38)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (3)
shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1)
  • NativeScaffold (26-114)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (1)
  • NativeScaffold (10-18)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)
  • NativeScaffold (28-89)
shared/sampletabbar/build.gradle.kts (1)
build-logic/convention/src/main/kotlin/extensions/DependencyExtensions.kt (1)
  • ktlintRuleset (35-36)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (3)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1)
  • title (6-11)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1)
  • title (15-20)
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)
  • toUIColor (93-99)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1)
  • SampleTabBarScreen (32-110)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)
  • tabs (29-32)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (4)
  • updateToolbar (3-7)
  • updateToolbar (4-4)
  • updateTabs (5-5)
  • updateSelectedTab (6-6)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (2)
  • createNativeScaffold (6-15)
  • createNativeScaffold (8-14)
android/app/src/main/kotlin/kmp/android/MainActivity.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1)
  • SampleTabBarScreen (32-110)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint & Build
  • GitHub Check: Run tests
🔇 Additional comments (21)
gradle/libs.versions.toml (1)

19-19: Compose plugin 1.9.0 is compatible with Kotlin 2.2.10
Kotlin 2.2.10 meets the Compose 1.9.0 requirement (K2 toolchain/Kotlin 2.1+), so no change is needed.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (5)

10-15: LGTM!

The struct declaration and initializer follow SwiftUI best practices for observable-backed views.


23-28: LGTM! The iOS 26.0 check is correct.

The version check for iOS 26.0 is intentional and aligns with the PR objectives, which specify that the liquid glass tab bar appearance is used on iOS 26+. The past review comment suggesting this is a typo is incorrect. The ignoresSafeArea() modifier on Line 25 enables the liquid glass effect for iOS 26+, while older versions (Line 27) use the native look without this modifier.


48-48: Verify hardcoded values are intentional.

The navigation title "Items" (Line 48) and badge count "3" (Line 64) are hardcoded. Please confirm whether these are placeholder values for demonstration purposes or if they should be driven by the observable or other dynamic data.

Also applies to: 64-64


54-56: Implement or document placeholder toolbar actions.

The toolbar button actions for Cancel (Lines 54-56) and Done (Lines 61-63) are empty placeholders. Please either implement these actions with appropriate logic (e.g., dismiss view, save state) or document that these are demonstration placeholders that will be implemented later.

Also applies to: 61-63


20-21: Ensure unique tab positions: Confirm that each BottomBarTabForIos instance defines a unique position, as duplicate values can cause TabView selection and rendering conflicts.

settings.gradle.kts (1)

34-34: LGTM!

The module inclusion follows the existing pattern and correctly adds the new shared sampletabbar module to the project structure.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1)

21-36: Verify the event handling implementation.

The view correctly hosts the Compose-based tab bar with proper frame and safe area modifiers. However, ensure that the event handling switch statement (lines 25-28) is intentionally minimal. If navigation or other side effects are expected, they should be implemented within the switch cases.

android/app/src/main/kotlin/kmp/android/MainActivity.kt (1)

36-58: LGTM!

The composable function correctly follows the standard Compose + ViewModel pattern with proper state collection and lifecycle management. The event handling placeholder is appropriate for this initial implementation.

shared/core/src/commonMain/kotlin/kmp/shared/core/di/Module.kt (2)

10-10: LGTM!

The import follows the existing pattern for module wiring.


26-26: LGTM!

The module addition is correctly placed and follows the established pattern for Koin DI wiring.

build-logic/convention/src/main/kotlin/config/KmmConfig.kt (1)

62-62: LGTM!

The module export correctly enables iOS framework access to the sampletabbar module, following the established pattern for other shared modules.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/di/Module.kt (1)

1-9: LGTM!

The Koin DI module correctly declares the ViewModel binding using idiomatic Koin DSL. The implementation follows best practices for dependency injection in Kotlin Multiplatform projects.

shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

1-4: LGTM!

The Android actual interface is correctly implemented as an empty interface, which is appropriate since Android doesn't require the platform-specific view factory methods needed on iOS. The comment provides useful context about the origin.

ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1)

10-14: LGTM!

The color bridging implementation correctly converts KMP colors to SwiftUI by delegating to the UIColor conversion. The pattern aligns with the iOS platform's color type hierarchy.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

1-7: LGTM!

The expect interface declaration follows standard KMM patterns for platform-specific implementations. The typealias provides a convenient alias for the interface.

ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift (1)

17-17: LGTM!

The new view model factory follows the established pattern and correctly resolves the dependency via the KMP container.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/LocalViewFactory.kt (1)

7-14: LGTM!

The composition local follows standard Compose patterns for dependency injection. The error message clearly indicates when the provider is missing.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (1)

3-7: LGTM!

The delegate interface provides a clean contract for iOS scaffold updates with clearly named methods for toolbar, tabs, and selection state.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

6-15: LGTM!

The iOS actual interface provides a well-defined factory method for native scaffold creation. The return type pairs the view controller with its delegate, enabling proper bidirectional communication between the scaffold and its content.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)

44-47: Fix OS availability check for blur: majorVersion >= 26 will never trigger; replace with the intended minimum iOS major version (e.g. ≥17).

Comment thread android/app/src/main/kotlin/kmp/android/MainActivity.kt
let main = UITabBarController()
main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()]
return main
@Injected(\.sampleTabBarViewModel) var viewModel: KMPShared.SampleTabBarViewModel

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused viewModel variable.

The viewModel is injected but never used. Either pass it to SampleTabBarView or remove the injection.

Apply this diff to remove the unused variable:

-        @Injected(\.sampleTabBarViewModel) var viewModel: KMPShared.SampleTabBarViewModel
         return UIHostingController(rootView: SampleTabBarView(flowController: self))
📝 Committable suggestion

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

Suggested change
@Injected(\.sampleTabBarViewModel) var viewModel: KMPShared.SampleTabBarViewModel
return UIHostingController(rootView: SampleTabBarView(flowController: self))
🤖 Prompt for AI Agents
In ios/Application/MainFlowController.swift around line 28, the Injected
property wrapper creates a viewModel that's not used; remove the unused
`@Injected(\.sampleTabBarViewModel) var viewModel:
KMPShared.SampleTabBarViewModel` declaration or instead pass `viewModel` into
the `SampleTabBarView` initializer where it's created/used. If removing, delete
that single line; if using, update the `SampleTabBarView(...)` call to accept
and forward `viewModel` as a parameter and remove any compiler warnings.

import KMPShared
import SwiftUI

class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Seal the observable.

Prevent subclassing for a delegate/observable type; improves clarity and performance.

-class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {
+final class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {
📝 Committable suggestion

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

Suggested change
class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {
final class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {
🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift
around line 9, the class declaration allows subclassing but should be sealed for
a delegate/observable type; change the declaration to a final class (e.g.,
replace "class NativeScaffoldObservable: ObservableObject,
NativeScaffoldDelegate {" with "final class NativeScaffoldObservable:
ObservableObject, NativeScaffoldDelegate {") to prevent subclassing and enable
related compiler optimizations.

Comment on lines +41 to +47
func updateTabs(tabs: [TabItem]) {
self.tabs = tabs
}

func updateSelectedTab(selectedTabPosition: Int32) {
self.selectedTab = selectedTabPosition
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Defensive: clamp selectedTab when tabs change.

If tabs shrink, selection can point to a non-existent index.

 func updateTabs(tabs: [TabItem]) {
     self.tabs = tabs
+    // Ensure selection stays valid
+    if !tabs.contains(where: { $0.position == selectedTab }) {
+        selectedTab = tabs.first?.position ?? 0
+    }
 }

Confirm whether positions are guaranteed contiguous; adjust clamp accordingly.

📝 Committable suggestion

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

Suggested change
func updateTabs(tabs: [TabItem]) {
self.tabs = tabs
}
func updateSelectedTab(selectedTabPosition: Int32) {
self.selectedTab = selectedTabPosition
}
func updateTabs(tabs: [TabItem]) {
self.tabs = tabs
// Ensure selection stays valid
if !tabs.contains(where: { $0.position == selectedTab }) {
selectedTab = tabs.first?.position ?? 0
}
}
🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift
around lines 41–47, updateTabs currently replaces self.tabs without adjusting
self.selectedTab, which can leave selectedTab pointing past the end when tabs
shrink; after assigning self.tabs = tabs, defensively clamp selectedTab: if
tabs.isEmpty set selectedTab to -1 (or your sentinel for “no selection”),
otherwise set selectedTab = max(0, min(selectedTab, Int32(tabs.count - 1))). If
tab positions are not guaranteed to be contiguous 0..n-1, instead compute a
valid selection by mapping/validating against the actual TabItem positions
(choose the nearest valid index or clear selection).

Comment on lines +17 to +54
var body: some View {
if let toolbar = observable.toolbar {
if #available(iOS 16.0, *) {
Group {
if !observable.tabs.isEmpty {
TabView(selection: $observable.selectedTab) {
ForEach(observable.tabs, id: \.position) { tab in
Group {
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(tab.position) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(tab.position) }
}
}
.tabItem {
if let uiImage = tab.icon.toUIImage() {
Image(uiImage: uiImage)
}
Text(tab.title)
}
.tag(tab.position)
}
}
} else {
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(nil) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(nil) }
}
}
}.toolbar(toolbar)
} else {
// Fallback on earlier versions
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Render content even when toolbar is nil (blank screen otherwise).

Currently, body shows nothing if observable.toolbar == nil. Show the content regardless; apply toolbar only when present.

-    var body: some View {
-        if let toolbar = observable.toolbar {
-            if #available(iOS 16.0, *) {
-                Group {
+    var body: some View {
+        if #available(iOS 16.0, *) {
+            Group {
                 // (content unchanged; see other diff to remove iOS 26 gating)
-                }.toolbar(toolbar)
-            } else {
-                // Fallback on earlier versions
-            }
-        }
+            }
+            .modifier(ToolbarWrapper(toolbar: observable.toolbar))
+        } else {
+            // Fallback on earlier versions
+            Group {
+                if !observable.tabs.isEmpty {
+                    TabView(selection: $observable.selectedTab) {
+                        ForEach(observable.tabs, id: \.position) { tab in
+                            ComposeViewController { observable.content(tab.position) }
+                                .ignoresSafeArea()
+                                .tabItem {
+                                    if let uiImage = tab.icon.toUIImage() { Image(uiImage: uiImage) }
+                                    Text(tab.title)
+                                }
+                                .tag(tab.position)
+                        }
+                    }
+                } else {
+                    ComposeViewController { observable.content(nil) }
+                        .ignoresSafeArea()
+                }
+            }
+        }

Add a small helper to conditionally apply toolbar:

+private struct ToolbarWrapper: ViewModifier {
+    let toolbar: Toolbar?
+    @available(iOS 16.0, *)
+    func body(content: Content) -> some View {
+        if let toolbar { content.toolbar(toolbar) } else { content }
+    }
+}
📝 Committable suggestion

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

Suggested change
var body: some View {
if let toolbar = observable.toolbar {
if #available(iOS 16.0, *) {
Group {
if !observable.tabs.isEmpty {
TabView(selection: $observable.selectedTab) {
ForEach(observable.tabs, id: \.position) { tab in
Group {
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(tab.position) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(tab.position) }
}
}
.tabItem {
if let uiImage = tab.icon.toUIImage() {
Image(uiImage: uiImage)
}
Text(tab.title)
}
.tag(tab.position)
}
}
} else {
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(nil) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(nil) }
}
}
}.toolbar(toolbar)
} else {
// Fallback on earlier versions
}
}
}
var body: some View {
if #available(iOS 16.0, *) {
Group {
if !observable.tabs.isEmpty {
TabView(selection: $observable.selectedTab) {
ForEach(observable.tabs, id: \.position) { tab in
Group {
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(tab.position) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(tab.position) }
}
}
.tabItem {
if let uiImage = tab.icon.toUIImage() {
Image(uiImage: uiImage)
}
Text(tab.title)
}
.tag(tab.position)
}
}
} else {
if #available(iOS 26.0, *) {
ComposeViewController { observable.content(nil) }
.ignoresSafeArea()
} else {
ComposeViewController { observable.content(nil) }
}
}
}
.modifier(ToolbarWrapper(toolbar: observable.toolbar))
} else {
// Fallback on earlier versions
Group {
if !observable.tabs.isEmpty {
TabView(selection: $observable.selectedTab) {
ForEach(observable.tabs, id: \.position) { tab in
ComposeViewController { observable.content(tab.position) }
.ignoresSafeArea()
.tabItem {
if let uiImage = tab.icon.toUIImage() {
Image(uiImage: uiImage)
}
Text(tab.title)
}
.tag(tab.position)
}
}
} else {
ComposeViewController { observable.content(nil) }
.ignoresSafeArea()
}
}
}
}
private struct ToolbarWrapper: ViewModifier {
let toolbar: Toolbar?
@available(iOS 16.0, *)
func body(content: Content) -> some View {
if let toolbar {
content.toolbar(toolbar)
} else {
content
}
}
}

Comment on lines +58 to +60
} else {
// Fallback on earlier versions
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider implementing the fallback or removing the version check.

The fallback branch for iOS versions earlier than 16.0 is empty. If this test screen is intended only for iOS 16.0+, consider removing the availability check. Otherwise, add a placeholder view (e.g., Text("Requires iOS 16.0+")) to provide a better user experience.

If iOS 16.0+ is required, apply this diff:

-    public var body: some View {
-        if #available(iOS 16.0, *) {
-            NavigationStack {
+    @available(iOS 16.0, *)
+    public var body: some View {
+        NavigationStack {

Then remove the closing brace for the if statement and the else clause.

Alternatively, if you want to support earlier versions, add a placeholder:

         } else {
-            // Fallback on earlier versions
+            Text("This feature requires iOS 16.0 or later")
+                .font(.headline)
+                .foregroundColor(.secondary)
         }

Comment on lines +25 to +28
val selectedTab = tabs.firstOrNull { it.position == selectedTabPosition }
Column(modifier = modifier) {
Box(modifier = Modifier.weight(1f)) {
selectedTab?.content?.invoke()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Handle the edge case when no tab matches the selected position.

If selectedTabPosition doesn't match any tab's position, selectedTab will be null and the content area will be empty. Consider adding a fallback, such as:

  • Defaulting to the first tab
  • Showing a placeholder/empty state
  • Adding a log/assertion to catch misconfiguration

Example:

-    val selectedTab = tabs.firstOrNull { it.position == selectedTabPosition }
+    val selectedTab = tabs.firstOrNull { it.position == selectedTabPosition } 
+        ?: tabs.firstOrNull() // Fallback to first tab
     Column(modifier = modifier) {
         Box(modifier = Modifier.weight(1f)) {
             selectedTab?.content?.invoke()
         }
📝 Committable suggestion

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

Suggested change
val selectedTab = tabs.firstOrNull { it.position == selectedTabPosition }
Column(modifier = modifier) {
Box(modifier = Modifier.weight(1f)) {
selectedTab?.content?.invoke()
val selectedTab = tabs.firstOrNull { it.position == selectedTabPosition }
?: tabs.firstOrNull() // Fallback to first tab
Column(modifier = modifier) {
Box(modifier = Modifier.weight(1f)) {
selectedTab?.content?.invoke()
}
}
🤖 Prompt for AI Agents
In
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt
around lines 25 to 28, selectedTab can be null if no tab matches
selectedTabPosition, leaving the content area empty; change the selection to use
a safe fallback (e.g., if matching tab is null, default to tabs.firstOrNull() or
a dedicated placeholder tab), and render a placeholder/empty state when there
are no tabs; also add a log or assertion when the match is missing to aid
debugging and catch misconfiguration.

Comment on lines +78 to +79
if (position != null) {
val tab = SampleTab.entries[position]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add bounds check for tab access.

Similar to the toolbar title, this direct array access can crash if position is out of range.

Apply this diff:

         if (position != null) {
-            val tab = SampleTab.entries[position]
+            val tab = SampleTab.entries.getOrNull(position) ?: return@NativeScaffold
             when (tab) {
📝 Committable suggestion

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

Suggested change
if (position != null) {
val tab = SampleTab.entries[position]
if (position != null) {
val tab = SampleTab.entries.getOrNull(position) ?: return@NativeScaffold
when (tab) {
🤖 Prompt for AI Agents
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt
around lines 78-79: the code directly indexes SampleTab.entries[position] which
can throw IndexOutOfBoundsException if position is outside 0..entries.lastIndex;
modify the block to check that position is within valid bounds (e.g., position
in SampleTab.entries.indices or position >= 0 && position <
SampleTab.entries.size) before accessing the array, and handle the invalid case
by using a safe fallback (null, a default tab, or early return) or logging an
error so the UI does not crash.

Comment on lines +82 to +86
val shapes = Shapes(
small = RoundedCornerShape(Values.Radius.large),
medium = RoundedCornerShape(Values.Radius.medium),
large = RoundedCornerShape(Values.Radius.small),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Shapes mapping appears inverted; align sizes.

Small/medium/large should map to corresponding radii; current mapping swaps small/large.

 val shapes = Shapes(
-    small = RoundedCornerShape(Values.Radius.large),
-    medium = RoundedCornerShape(Values.Radius.medium),
-    large = RoundedCornerShape(Values.Radius.small),
+    small = RoundedCornerShape(Values.Radius.small),
+    medium = RoundedCornerShape(Values.Radius.medium),
+    large = RoundedCornerShape(Values.Radius.large),
 )

Please confirm this matches intended design specs.


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
# Locate the definition of Radius to verify intended size values
rg -nP 'enum class Radius' -C5 --type kotlin

Length of output: 0


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
echo "### Searching for Values definitions ###"
rg -n "object Values" -C5 --type kotlin
echo "### Searching for Radius references ###"
rg -n "Radius" -C5 --type kotlin

Length of output: 13870


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
# Show top 30 lines of SampleTheme.kt to determine the import of Values
sed -n '1,30p' shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt

Length of output: 1129


Align shape corner radii with Radius definitions
Swap small and large mappings so they match Values.Radius:

 val shapes = Shapes(
-    small = RoundedCornerShape(Values.Radius.large),
-    medium = RoundedCornerShape(Values.Radius.medium),
-    large = RoundedCornerShape(Values.Radius.small),
+    small = RoundedCornerShape(Values.Radius.small),
+    medium = RoundedCornerShape(Values.Radius.medium),
+    large = RoundedCornerShape(Values.Radius.large),
 )
📝 Committable suggestion

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

Suggested change
val shapes = Shapes(
small = RoundedCornerShape(Values.Radius.large),
medium = RoundedCornerShape(Values.Radius.medium),
large = RoundedCornerShape(Values.Radius.small),
)
val shapes = Shapes(
small = RoundedCornerShape(Values.Radius.small),
medium = RoundedCornerShape(Values.Radius.medium),
large = RoundedCornerShape(Values.Radius.large),
)
🤖 Prompt for AI Agents
In
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt
around lines 82 to 86, the small and large shape mappings are swapped against
the Values.Radius definitions; change the Shapes initialization so small uses
Values.Radius.small, medium stays Values.Radius.medium, and large uses
Values.Radius.large to align the corner radii with the Radius constants.

@JuliaJakubcova JuliaJakubcova force-pushed the feature/kmm/cmp-liquid-glass branch from 16ede13 to cb2afed Compare October 9, 2025 06:54

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

♻️ Duplicate comments (33)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (3)

23-23: Fix the grammatical error.

The text uses "it's" (contraction of "it is") but should use "its" (possessive) to indicate the item's body.

Apply this diff to fix the typo:

-                                Text("And this is it's body")
+                                Text("And this is its body")

51-55: Remove the duplicate toolbar item.

Lines 51-55 are an exact duplicate of lines 45-49, creating two identical "Second" buttons in the trailing position. This appears to be a copy-paste error.

Apply this diff to remove the duplicate:

                    ToolbarItemGroup(placement: .topBarTrailing) {
                        Button("Second", systemImage: "person") {
                            
                        }
                    }
-                    
-                    ToolbarItemGroup(placement: .topBarTrailing) {
-                        Button("Second", systemImage: "person") {
-                            
-                        }
-                    }
                }

58-60: Consider implementing the fallback or removing the version check.

The fallback branch for iOS versions earlier than 16.0 is empty. If this test screen is intended only for iOS 16.0+, consider removing the availability check. Otherwise, add a placeholder view (e.g., Text("Requires iOS 16.0+")) to provide a better user experience.

If iOS 16.0+ is required, apply this diff:

-    public var body: some View {
-        if #available(iOS 16.0, *) {
-            NavigationStack {
+    @available(iOS 16.0, *)
+    public var body: some View {
+        NavigationStack {

Then remove the closing brace for the if statement and the else clause.

Alternatively, if you want to support earlier versions, add a placeholder:

         } else {
-            // Fallback on earlier versions
+            Text("This feature requires iOS 16.0 or later")
+                .font(.headline)
+                .foregroundColor(.secondary)
         }
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)

50-50: Still using hardcoded Color.White instead of theme color.

This issue was previously flagged but hasn't been addressed. Line 50 should use backgroundColor (already defined on line 42) or MaterialTheme.colorScheme.surface to respect dark mode and theme consistency, as noted in the earlier review.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (4)

23-28: iOS 26.0 does not exist; conditional logic breaks intended behavior.

Line 23 checks for iOS 26.0, which does not exist as of October 2025. This prevents the "liquid glass" appearance from ever being applied. Additionally, .ignoresSafeArea() has been available since iOS 13, so conditionally applying it only on iOS 26+ serves no purpose.

Apply this diff to use a realistic iOS version (assuming iOS 16+ targets the "liquid glass" effect):

-                            if #available(iOS 26.0, *) {
+                            if #available(iOS 16.0, *) {
                                 ComposeViewController { tab.content }
                                     .ignoresSafeArea()
                             } else {

If the liquid glass effect requires a specific iOS version, verify the correct minimum version from Apple's documentation and update accordingly.


30-36: Remove commented-out code.

This commented-out ScrollView block was previously flagged for removal and marked as addressed, but it remains in the code. Please remove it to maintain code cleanliness.


49-49: Remove commented-out duplicate.

This commented line duplicates the active setting on Line 50 and was previously flagged for removal but remains in the code.


68-70: Implement fallback for earlier iOS versions.

The fallback block for iOS versions below 16.0 remains empty, which was previously flagged. Users on those versions will see nothing.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)

82-85: Align Shapes with radius naming

small/large are still swapped. Map each size to its matching Values.Radius.

android/app/src/main/kotlin/kmp/android/MainActivity.kt (1)

29-29: Remove commented-out code.

The commented Root() call should be removed to maintain code cleanliness.

Apply this diff:

-//                Root()
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (2)

50-55: Avoid repeated WindowInsets computation.

Compute WindowInsets.safeContent.asPaddingValues() once for readability and performance.

Apply this diff:

                     if (showBlur) {
+                        val safePadding = WindowInsets.safeContent.asPaddingValues()
                         BlurredContainer(
-                            top = WindowInsets.safeContent.asPaddingValues().calculateTopPadding(),
-                            bottom = WindowInsets.safeContent.asPaddingValues()
-                                .calculateBottomPadding(),
+                            top = safePadding.calculateTopPadding(),
+                            bottom = safePadding.calculateBottomPadding(),
                             modifier = Modifier.fillMaxSize(),
                         ) {
-                            content(position, WindowInsets.safeContent.asPaddingValues())
+                            content(position, safePadding)
                         }

78-80: Use LaunchedEffect for side effects.

Using remember to trigger delegate updates is an anti-pattern. Use LaunchedEffect keyed by the inputs.

Apply this diff:

-    remember(toolbar) { delegate.updateToolbar(toolbar) }
-    remember(tabs) { delegate.updateTabs(tabs) }
-    remember(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) }
+    LaunchedEffect(toolbar) { delegate.updateToolbar(toolbar) }
+    LaunchedEffect(tabs) { delegate.updateTabs(tabs) }
+    LaunchedEffect(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) }
ios/Application/MainFlowController.swift (2)

28-28: Remove unused viewModel variable.

The viewModel is injected but never used. Either pass it to SampleTabBarView or remove the injection.

Apply this diff to remove the unused variable:

-        @Injected(\.sampleTabBarViewModel) var viewModel: KMPShared.SampleTabBarViewModel
         return UIHostingController(rootView: SampleTabBarView(flowController: self))

30-33: Remove commented-out code.

The commented alternative implementations should be removed to maintain code cleanliness.

Apply this diff:

-//        return UIHostingController(rootView: TestScreen())
-//        let main = UITabBarController()
-//        main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()]
-//        return main
shared/core/build.gradle.kts (1)

42-43: Remove duplicate dependency declaration.

The sampletabbar dependency is declared twice, which can cause build issues or confusion.

Apply this diff to remove the duplicate:

 commonMainApi(project(":shared:sampletabbar"))
-commonMainApi(project(":shared:sampletabbar"))
shared/sampletabbar/build.gradle.kts (1)

31-31: Avoid shipping tooling deps in release builds.

compose.components.uiToolingPreview is a debug-only tool and should not be included in release artifacts. Move it to a debug-specific configuration.

Apply this diff to move it to debug configuration:

-    commonMainImplementation(compose.components.uiToolingPreview)
+    // Move to debug configuration - adjust based on your setup:
+    // For common debug: commonDebugImplementation(compose.components.uiToolingPreview)
+    // Or configure debug-specific source sets
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)

9-12: Seal the factory with final.

No subclassing is intended; marking the class final prevents unintended inheritance and enables compiler optimizations.

Apply this diff:

-public class SwiftUIViewFactory: ComposeViewFactory {
+public final class SwiftUIViewFactory: ComposeViewFactory {
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1)

13-19: Remove unused flowController parameter.

The flowController is stored but never used. Either integrate it for navigation or remove it to avoid dead parameters.

Apply this diff if no navigation is needed:

-    private weak var flowController: FlowController?
-    
     @State private var toastData: ToastData?
     
-    public init(flowController: FlowController?) {
-        self.flowController = flowController
+    public init() {
     }

Or if navigation is needed, reference flowController in the event handler at line 27 or pass it to child components.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)

1-1: Add missing package declaration.

The file is missing a package declaration, which violates Kotlin conventions and triggers a detekt warning. Add the package declaration matching the module path.

Apply this diff:

+package kmp.shared.sampletabbar.presentation
+
 import androidx.compose.runtime.CompositionLocalProvider
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (3)

24-35: Critical: Missing toolbar initialization.

The toolbar property is never assigned in the initializer, leaving it uninitialized. This will cause undefined behavior or a crash when the observable is used.

Apply this diff to fix:

     init(
         toolbar: Toolbar?,
         tabs: [TabItem],
         selectedTab: Int32,
         onSelectedTabChanged: @escaping (Int32) -> Void,
         content: @escaping (Int32?) -> UIViewController
     ) {
+        self.toolbar = toolbar
         self.tabs = tabs
         self.selectedTab = selectedTab
         self.onSelectedTabChanged = onSelectedTabChanged
         self.content = content
     }

9-9: Seal the observable with final.

Prevent subclassing for this delegate/observable type to improve clarity and enable compiler optimizations.

Apply this diff:

-class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {
+final class NativeScaffoldObservable: ObservableObject, NativeScaffoldDelegate {

41-43: Defensive: clamp selectedTab when tabs change.

If the tabs array shrinks, selectedTab can point to a non-existent index. Consider validating and adjusting the selection after updating tabs.

Apply this diff:

 func updateTabs(tabs: [TabItem]) {
     self.tabs = tabs
+    // Ensure selection stays valid
+    if !tabs.contains(where: { $0.position == selectedTab }) {
+        selectedTab = tabs.first?.position ?? 0
+    }
 }

Note: Adjust the clamping logic if tab positions are not guaranteed to be contiguous.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)

11-15: Guard selectedTabPosition against out-of-range indices.

The OnTabSelected intent updates selectedTabPosition directly without validating that intent.index is within the valid range of tabs. This can lead to out-of-bounds exceptions in UI code that directly indexes SampleTab.entries[state.selectedTabPosition].

Apply this diff to add bounds checking:

-            is SampleTabBarIntent.OnTabSelected -> update { copy(selectedTabPosition = intent.index) }
+            is SampleTabBarIntent.OnTabSelected -> update {
+                val max = tabs.lastIndex
+                val safeIndex = if (max >= 0) intent.index.coerceIn(0, max) else 0
+                copy(selectedTabPosition = safeIndex)
+            }
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (5)

1-1: Add missing package declaration.

The file is missing its package declaration, which triggers a detekt warning. The package statement must be the first non-comment line in the file.

Apply this diff:

+package kmp.shared.sampletabbar.presentation.ui
+
 import androidx.compose.foundation.layout.Column

19-19: Remove unused Haze import.

The ExperimentalHazeMaterialsApi import is unused in this file; the iOS actual implementation handles haze effects.

Apply this diff:

-import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi

Also remove from the opt-in annotation on line 112:

-@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
+@OptIn(ExperimentalMaterial3Api::class)

41-41: Make toolbar title resilient to invalid index.

Direct array access can crash if selectedTabPosition is out of range. Use safe access with a fallback.

Apply this diff:

-            title = SampleTab.entries[state.selectedTabPosition].title,
+            title = state.tabs.getOrNull(state.selectedTabPosition)?.title ?: "",

78-79: Add bounds check for tab content access.

Direct indexing can throw IndexOutOfBoundsException if position is out of range.

Apply this diff:

         if (position != null) {
-            val tab = SampleTab.entries[position]
+            val tab = SampleTab.entries.getOrNull(position) ?: return@NativeScaffold
             when (tab) {

94-94: Fix possessive typo: "it's" → "its".

Grammatical error in user-facing text.

Apply this diff:

-                        text = "And this is it's body",
+                        text = "And this is its body",
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (2)

17-54: Render content even when toolbar is nil.

The entire body is wrapped in if let toolbar, causing a blank screen when toolbar is nil. Content should always render; the toolbar should be optional decoration.

Apply this diff to always render content and conditionally apply toolbar:

     var body: some View {
-        if let toolbar = observable.toolbar {
-            if #available(iOS 16.0, *) {
-                Group {
+        if #available(iOS 16.0, *) {
+            Group {
                     if !observable.tabs.isEmpty {
                         TabView(selection: $observable.selectedTab) {
                             ForEach(observable.tabs, id: \.position) { tab in
-                                Group {
-                                    if #available(iOS 26.0, *) {
-                                        ComposeViewController { observable.content(tab.position) }
-                                            .ignoresSafeArea()
-                                    } else {
-                                        ComposeViewController { observable.content(tab.position) }
-                                    }
-                                }
+                                ComposeViewController { observable.content(tab.position) }
+                                    .ignoresSafeArea()
                                 .tabItem {
                                     if let uiImage = tab.icon.toUIImage() {
                                         Image(uiImage: uiImage)
@@ -38,16 +29,13 @@
                             }
                         }
                     } else {
-                        if #available(iOS 26.0, *) {
-                            ComposeViewController { observable.content(nil) }
-                                .ignoresSafeArea()
-                        } else {
-                            ComposeViewController { observable.content(nil) }
-                        }
+                        ComposeViewController { observable.content(nil) }
+                            .ignoresSafeArea()
                     }
-                }.toolbar(toolbar)
-            } else {
-                // Fallback on earlier versions
             }
+            .modifier(ToolbarWrapper(toolbar: observable.toolbar))
+        } else {
+            // Fallback on earlier versions
         }
     }
 }
+
+private struct ToolbarWrapper: ViewModifier {
+    let toolbar: Toolbar?
+    @available(iOS 16.0, *)
+    func body(content: Content) -> some View {
+        if let toolbar { content.toolbar(toolbar) } else { content }
+    }
+}

25-31: Fix invalid availability check: iOS 26.0 does not exist.

The availability check #available(iOS 26.0, *) will never evaluate to true on any current iOS version. Apply .ignoresSafeArea() unconditionally (it's available from iOS 14+).

Apply this diff (also fixes lines 42-47):

                             ForEach(observable.tabs, id: \.position) { tab in
-                                Group {
-                                    if #available(iOS 26.0, *) {
-                                        ComposeViewController { observable.content(tab.position) }
-                                            .ignoresSafeArea()
-                                    } else {
-                                        ComposeViewController { observable.content(tab.position) }
-                                    }
-                                }
+                                ComposeViewController { observable.content(tab.position) }
+                                    .ignoresSafeArea()
                                 .tabItem {

Also for the empty tabs case:

                     } else {
-                        if #available(iOS 26.0, *) {
-                            ComposeViewController { observable.content(nil) }
-                                .ignoresSafeArea()
-                        } else {
-                            ComposeViewController { observable.content(nil) }
-                        }
+                        ComposeViewController { observable.content(nil) }
+                            .ignoresSafeArea()
                     }
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)

9-17: Document the nullability of currentTabPosition.

The currentTabPosition parameter in the content lambda is nullable, but the function lacks documentation explaining when it will be null (i.e., when tabs.isEmpty()).

Add KDoc above the function:

+/**
+ * Platform-specific scaffold with optional toolbar and bottom tab navigation.
+ *
+ * @param currentTabPosition Passed to [content] as `null` when [tabs] is empty,
+ *   otherwise contains the currently selected tab index.
+ */
 @Composable
 expect fun NativeScaffold(

24-30: Avoid function properties in data classes.

Including the onClick lambda in a data class breaks value equality semantics. Two instances with identical data but different lambda references won't be equal, and equals()/hashCode()/copy() will include the lambda reference.

Apply this diff to convert to a regular class:

-data class ToolbarButtonData(
+class ToolbarButtonData(
     val icon: ImageResource,
     val description: String? = null,
     val position: ToolbarButtonPosition = ToolbarButtonPosition.Trailing,
     val tint: NativeColor? = null,
     val onClick: () -> Unit,
 )

41-45: Consider removing redundant position field from TabItem.

Since tabs are passed as a List<TabItem>, the position is implicitly defined by the list index. Storing an explicit position field creates a risk of inconsistency if the list is reordered.

Apply this diff:

 data class TabItem(
     val title: String,
     val icon: ImageResource,
-    val position: Int,
 )

Then update call sites to use tabs.mapIndexed { index, tab -> ... } to derive position from the index.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16ede13 and cb2afed.

📒 Files selected for processing (32)
  • android/app/src/main/kotlin/kmp/android/MainActivity.kt (2 hunks)
  • build-logic/convention/src/main/kotlin/config/KmmConfig.kt (1 hunks)
  • gradle/libs.versions.toml (4 hunks)
  • ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift (1 hunks)
  • ios/Application/MainFlowController.swift (2 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1 hunks)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (1 hunks)
  • ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1 hunks)
  • settings.gradle.kts (1 hunks)
  • shared/core/build.gradle.kts (1 hunks)
  • shared/core/src/commonMain/kotlin/kmp/shared/core/di/Module.kt (2 hunks)
  • shared/samplecomposemultiplatform/build.gradle.kts (1 hunks)
  • shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt (1 hunks)
  • shared/sampletabbar/build.gradle.kts (1 hunks)
  • shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/di/Module.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/LocalViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1 hunks)
  • shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift (3)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
shared/samplecomposemultiplatform/src/iosMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/BottomBarTabForIos.kt (1)
  • title (6-11)
shared/samplecomposemultiplatform/src/commonMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.kt (1)
  • title (15-20)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (2)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (2)
  • index (34-37)
  • index (36-36)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (2)
  • createNativeScaffold (6-15)
  • createNativeScaffold (8-14)
shared/sampletabbar/build.gradle.kts (1)
build-logic/convention/src/main/kotlin/extensions/DependencyExtensions.kt (1)
  • ktlintRuleset (35-36)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (2)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)
  • SampleTabBarViewController (17-47)
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/View+Extensions.swift (1)
  • toastView (36-38)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1)
  • SampleTabBarScreen (32-110)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)
  • tabs (29-32)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)
  • icon (24-30)
  • title (19-22)
  • title (41-45)
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift (1)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)
  • toUIColor (91-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (3)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (1)
  • NativeScaffold (9-17)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (1)
  • NativeScaffold (26-87)
shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt (1)
  • NativeScaffold (26-114)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (1)
  • tabs (29-32)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (4)
  • updateToolbar (3-7)
  • updateToolbar (4-4)
  • updateTabs (5-5)
  • updateSelectedTab (6-6)
android/app/src/main/kotlin/kmp/android/MainActivity.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (1)
  • SampleTabBarScreen (32-110)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt (2)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt (1)
  • SampleTheme (88-97)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)
  • BlurredContainer (26-88)
🪛 detekt (1.23.8)
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt

[warning] 1-48: The file does not contain a package declaration.

(detekt.potential-bugs.MissingPackageDeclaration)

🔇 Additional comments (28)
android/app/src/main/kotlin/kmp/android/MainActivity.kt (1)

36-58: LGTM: SampleTabBarMainRoute implementation.

The composable correctly collects state, handles the OnAppeared intent, and processes events. The empty else branch in the event handler is acceptable as it handles the exhaustive when expression.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

6-15: LGTM: iOS ComposeViewFactory interface.

The actual interface correctly defines the iOS-specific factory contract for creating native scaffold views with their delegates.

shared/samplecomposemultiplatform/build.gradle.kts (1)

27-27: LGTM: Material3 dependency addition.

The addition of Material3 Compose dependency is appropriate for the new sample tab bar feature.

shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)

1-4: LGTM: Android ComposeViewFactory interface.

The empty actual interface is appropriate for Android, where the factory pattern may not be needed since Android uses direct Compose implementation without UIViewController bridging.

gradle/libs.versions.toml (2)

43-44: No action needed for Coil and Haze updates. Coil 3.3.0 and Haze 1.6.10 are the latest published versions and have no known security advisories as of 2025-10-09.


19-19: No action needed: Compose plugin version 1.9.0 is the latest release and compatible with Kotlin 2.2.10.

ios/Application/DependencyInjection/Sources/DependencyInjection/KMPViewModels.swift (1)

17-17: LGTM!

The factory registration follows the established pattern and correctly wires the KMM SampleTabBarViewModel into the iOS DI container.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffoldDelegate.kt (1)

3-7: LGTM!

The delegate interface provides a clean contract for bridging Kotlin/Multiplatform state to iOS platform scaffolding. The method signatures are well-defined and align with the iOS observable pattern.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/LocalViewFactory.kt (1)

7-14: LGTM!

The CompositionLocal follows the standard pattern with a fail-fast default that ensures LocalViewFactory is properly provided by consumers. The error message is clear and actionable.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)

13-32: LGTM!

The scaffold creation logic correctly:

  • Wraps Swift callbacks with KotlinInt for platform bridging
  • Wires the observable to a SwiftUI view via UIHostingController
  • Returns the paired controller and delegate as expected by the KMM contract
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/SampleTabBarView.swift (1)

21-36: LGTM!

The view correctly:

  • Wraps the Compose controller with proper event forwarding
  • Applies full-screen layout and safe area handling
  • Integrates toast presentation

The default event case provides safe fallback behavior.

shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/SampleTabBarViewController.kt (1)

17-47: LGTM!

The controller implementation correctly:

  • Obtains the view model via Koin
  • Observes state with lifecycle awareness
  • Dispatches OnAppeared on view creation
  • Forwards events to the callback
  • Provides LocalViewFactory and theme context
  • Passes state and intent handler to the screen
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (5)

18-26: LGTM!

The lazy initialization pattern is appropriate, ensuring tabs are populated only once when the screen appears.


29-32: LGTM!

Clean state definition with sensible defaults.


34-37: LGTM!

Intent structure follows MVVM best practices with clear separation of user actions.


39-39: LGTM!

Empty event interface is acceptable as a placeholder for future one-time events (e.g., navigation, snackbars).


41-43: LGTM!

Straightforward enum definition for tab types.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTabBarScreen.kt (5)

32-38: LGTM!

Clean composable signature following established patterns with state and intent handling.


42-63: LGTM!

Toolbar buttons are properly configured with placeholder click handlers, which is appropriate for demonstration purposes.


65-76: LGTM!

Tab configuration and selection handler are properly wired to the ViewModel.


112-135: LGTM!

Reusable scrollable content helper is well-structured.


137-149: LGTM!

Extension properties provide clean abstraction for tab metadata.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (3)

10-16: LGTM!

Standard SwiftUI view initialization with observable pattern.


57-86: LGTM!

Toolbar extension properly creates a NavigationStack and maps toolbar data to SwiftUI buttons.


88-100: LGTM!

Button rendering helper properly handles image template mode and color tinting.

shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.kt (3)

19-22: LGTM!

Clean data class definition for toolbar configuration.


32-34: LGTM!

Clean expect class for platform-specific color handling.


36-39: LGTM!

Straightforward enum for button positioning.

@@ -1,13 +1,19 @@
package kmp.android

import SampleTabBarScreen

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix unqualified import statement.

The import statement import SampleTabBarScreen lacks a package qualifier, which is non-standard and may cause compilation issues or ambiguity.

Apply this diff to add the proper package:

-import SampleTabBarScreen
+import kmp.shared.sampletabbar.presentation.ui.SampleTabBarScreen
📝 Committable suggestion

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

Suggested change
import SampleTabBarScreen
import kmp.shared.sampletabbar.presentation.ui.SampleTabBarScreen
🤖 Prompt for AI Agents
In android/app/src/main/kotlin/kmp/android/MainActivity.kt around line 3, the
import "import SampleTabBarScreen" is unqualified and must be replaced with the
fully qualified package path for SampleTabBarScreen; find the package
declaration in the file where SampleTabBarScreen is defined (or its module
namespace) and change the import to that full package-qualified name (for
example replace with com.your.package.path.SampleTabBarScreen), then save and
rebuild to verify the import resolves.

Comment on lines +48 to +66
.navigationTitle("Items")
// .toolbarTitleDisplayMode(.inline)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel", systemImage: "xmark") {
// cancel action
}
.tint(.red)
}

ToolbarItem(placement: .topBarTrailing) {
Button("Done", systemImage: "checkmark") {
// done action
}
.badge(3)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Implement button actions and avoid hardcoded values.

The toolbar contains several issues:

  • Lines 54-56 and 61-63: Button actions are empty placeholders
  • Line 48: Navigation title "Items" is hardcoded
  • Line 64: Badge value 3 is hardcoded

Consider:

  1. Wire button actions to observable callbacks or remove the buttons if not needed
  2. Make the navigation title configurable via the observable
  3. Make the badge value dynamic or remove it if it's test code

Example refactor for making the title configurable:

-                .navigationTitle("Items")
+                .navigationTitle(observable.title)
📝 Committable suggestion

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

Suggested change
.navigationTitle("Items")
// .toolbarTitleDisplayMode(.inline)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel", systemImage: "xmark") {
// cancel action
}
.tint(.red)
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done", systemImage: "checkmark") {
// done action
}
.badge(3)
}
}
// := Lines 48–66 in ScreenWithBottomBarView.swift
.navigationTitle(observable.title)
// .toolbarTitleDisplayMode(.inline)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel", systemImage: "xmark") {
// cancel action
}
.tint(.red)
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done", systemImage: "checkmark") {
// done action
}
.badge(3)
}
}
🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/ScreenWithBottomBarView.swift
around lines 48 to 66, replace hardcoded UI values and empty button actions by
wiring them to the view model / observable: read the navigation title from the
observable (instead of the literal "Items"), read the badge count from an
observable Int (or hide the badge when nil/zero) instead of the hardcoded 3, and
call observable methods (e.g., cancelTapped() and doneTapped()) from the Cancel
and Done Button actions (or remove the buttons if the observable has no
handlers); ensure UI updates occur on the main thread and handle
optional/invalid values safely.

Comment on lines +10 to +14
public extension Color {
init(kmpColor: KMPShared.NativeColor) {
self = Color(uiColor: kmpColor.toUIColor())
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

LGTM! Verify iOS deployment target for Color(uiColor:).

The implementation correctly bridges KMP colors to SwiftUI by delegating to the existing toUIColor() method. This approach is clean and maintains consistency.

The Color(uiColor:) initializer requires iOS 15.0+. Please verify that the minimum deployment target for this module supports iOS 15.0 or higher. If the project supports earlier iOS versions, you'll need to add an availability annotation or provide a fallback implementation.

Run this script to check the iOS deployment target:


🏁 Script executed:

#!/bin/bash
# Description: Check minimum iOS deployment target in Xcode project and Package.swift

# Check for deployment target in Xcode project files
echo "=== Checking Xcode project deployment targets ==="
fd -e pbxproj --exec grep -H "IPHONEOS_DEPLOYMENT_TARGET"

# Check for deployment target in Package.swift if using SPM
echo -e "\n=== Checking Package.swift for platform requirements ==="
fd -g "Package.swift" --exec grep -A 2 "platforms:"

Length of output: 2676


LGTM—adjust iOS version compatibility
The init(kmpColor:) extension uses Color(uiColor:) (iOS 15+). Update the Swift package manifest to require iOS 15.0, or annotate the initializer with @available(iOS 15, *) and provide a fallback for earlier OS versions.

🤖 Prompt for AI Agents
ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Color+Extensions.swift
lines 10-14: The initializer uses Color(uiColor:) which is only available on iOS
15+, so either update the Swift package manifest (Package.swift / platform
declarations) to require .iOS(.v15) or mark the initializer with @available(iOS
15, *) and add a fallback path for earlier OS versions (e.g., convert the
UIColor to compatible components and construct a Color using an alternative
initializer or guard availability and provide a noop/compatible default). Ensure
the chosen approach is applied consistently across the package manifest or the
extension and that tests/build targets reflect the new availability.

Comment on lines +47 to +49
modifier = Modifier.padding(
bottom = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding(),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stop padding each item with system insets

Applying WindowInsets.systemBars to every NavigationBarItem makes each item unnecessarily tall and can double-apply bottom insets. Let NavigationBar handle system bars (or apply windowInsetsPadding to the bar itself) and drop the per-item padding.

🤖 Prompt for AI Agents
In
shared/samplecomposemultiplatform/src/androidMain/kotlin/kmp/shared/samplecomposemultiplatform/presentation/ui/ScreenWithPlatformSpecificBottomBar.android.kt
around lines 47-49, each NavigationBarItem is being padded with
WindowInsets.systemBars which makes items taller and may double-apply bottom
insets; remove the per-item Modifier.padding(...) that uses
WindowInsets.systemBars and instead apply the system bar insets to the container
NavigationBar (e.g., add Modifier.windowInsetsPadding(WindowInsets.systemBars)
or rely on NavigationBar's built-in handling) so the bar receives the inset once
and items use normal sizing.

Comment on lines +37 to +112
Scaffold(
modifier = modifier,
topBar = {
toolbar?.let {
CenterAlignedTopAppBar(
title = {
toolbar.title?.let { title ->
Text(title)
}
},
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.5f),
),
navigationIcon = {
toolbar.buttons
.filter { it.position == ToolbarButtonPosition.Leading }
.forEach { button ->
IconButton(button.onClick) {
Icon(
painter = painterResource(button.icon.drawableResId),
contentDescription = button.description,
tint = button.tint?.composeColor
?: LocalContentColor.current,
)
}
}
},
actions = {
toolbar.buttons
.filter { it.position == ToolbarButtonPosition.Trailing }
.forEach { button ->
IconButton(button.onClick) {
Icon(
painter = painterResource(button.icon.drawableResId),
contentDescription = button.description,
tint = button.tint?.composeColor
?: LocalContentColor.current,
)
}
}
},
modifier = Modifier,
)
}
},
bottomBar = {
AnimatedVisibility(visible = tabs.isNotEmpty()) {
NavigationBar(
modifier = Modifier.fillMaxWidth(),
) {
tabs.forEach { tab ->
NavigationBarItem(
selected = selectedTabPosition == tab.position,
icon = {
Icon(
painter = painterResource(tab.icon.drawableResId),
contentDescription = tab.title,
)
},
onClick = { onTabSelected(tab.position) },
label = {
Text(tab.title)
},
modifier = Modifier.padding(
bottom = WindowInsets.systemBars.asPaddingValues()
.calculateBottomPadding(),
),
)
}
}
}
},
) { contentPadding ->
content(selectedTabPosition.takeIf { tabs.isNotEmpty() }, contentPadding)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hook scrollBehavior into nested scroll

TopAppBarDefaults.enterAlwaysScrollBehavior() won’t respond without attaching modifier.nestedScroll(scrollBehavior.nestedScrollConnection) to the Scaffold (or parent). Add the nested scroll hookup so the toolbar collapses as intended.

🤖 Prompt for AI Agents
shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt
lines 37-112: the Scaffold currently uses the passed-in modifier but does not
attach the nested scroll connection, so
TopAppBarDefaults.enterAlwaysScrollBehavior() won’t work; update the Scaffold
modifier to include nestedScroll(scrollBehavior.nestedScrollConnection) when
scrollBehavior is non-null (e.g., modifier.then(Modifier.nestedScroll(...)) or
modifier.nestedScroll(...) depending on nullability) so the toolbar’s scroll
behavior is hooked into the Scaffold.

Comment on lines +87 to +105
modifier = Modifier.fillMaxWidth(),
) {
tabs.forEach { tab ->
NavigationBarItem(
selected = selectedTabPosition == tab.position,
icon = {
Icon(
painter = painterResource(tab.icon.drawableResId),
contentDescription = tab.title,
)
},
onClick = { onTabSelected(tab.position) },
label = {
Text(tab.title)
},
modifier = Modifier.padding(
bottom = WindowInsets.systemBars.asPaddingValues()
.calculateBottomPadding(),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove system-bar padding from each NavigationBarItem

Same issue as the shared bottom-bar implementation: padding every item with WindowInsets.systemBars bloats item height and double-applies insets. Let NavigationBar manage insets (or apply them once to the bar).

🤖 Prompt for AI Agents
In
shared/sampletabbar/src/androidMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.android.kt
around lines 87 to 105, each NavigationBarItem applies WindowInsets.systemBars
bottom padding which increases item height and duplicates insets; remove the
Modifier.padding(...) call that uses
WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() from the
individual NavigationBarItem modifiers and instead apply the system bar inset
once to the parent NavigationBar (or its container) using a single
navigation-bars inset modifier (e.g., navigationBarsPadding() or equivalent) so
insets are applied exactly once to the bar, not per item.

Comment on lines +26 to +33
@OptIn(ExperimentalHazeMaterialsApi::class)
@Composable
fun BlurredContainer(
top: Dp = 0.dp,
bottom: Dp = 0.dp,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding KDoc documentation.

This public composable lacks documentation explaining its purpose, parameters (especially top and bottom), and usage. Adding KDoc would improve API clarity and maintainability.

Example:

/**
 * A container that applies a blur effect to its content with optional decorative gradient bars.
 *
 * @param top Height of the top gradient bar with blur effect. Set to 0.dp to hide.
 * @param bottom Height of the bottom gradient bar. Set to 0.dp to hide.
 * @param modifier Modifier to be applied to the container.
 * @param content The content to be displayed and used as the blur source.
 */
@OptIn(ExperimentalHazeMaterialsApi::class)
@Composable
fun BlurredContainer(
    top: Dp = 0.dp,
    bottom: Dp = 0.dp,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
🤖 Prompt for AI Agents
In
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt
around lines 26 to 33, the public @Composable BlurredContainer is missing KDoc
describing its purpose and parameters; add a KDoc block immediately above the
function that succinctly explains what the composable does, documents the top
and bottom Dp parameters (e.g., heights of optional gradient/blur bars and that
0.dp hides them), the modifier, and the content lambda, and include a brief
usage note or example line if helpful.

)

val typography = Typography(
// Define typohraphy

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typo in comment

The inline comment should read “typography.”

🤖 Prompt for AI Agents
In
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/SampleTheme.kt
around line 79, the inline comment contains a typo ("typohraphy"); update that
comment text to read "typography" so the comment is spelled correctly.

Comment on lines +45 to +47
val showBlur = NSProcessInfo.processInfo.operatingSystemVersion.useContents {
this.majorVersion >= 26
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify iOS version check.

The condition majorVersion >= 26 appears incorrect. iOS version numbers are typically in the teens (e.g., iOS 15, 16, 17, 18). If this is checking for iOS 18+, the condition should likely be >= 18. iOS 26 would be many years in the future.


🏁 Script executed:

#!/bin/bash
# Search for any documentation or comments explaining this version check
rg -n -C3 "majorVersion.*26|iOS.*26" --type=kt

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for the iOS version check in Kotlin files without relying on rg type definitions
rg -n -C3 --glob "*.kt" "majorVersion >= 26"

Length of output: 1019


Correct iOS version threshold for blur effect.
The check this.majorVersion >= 26 is invalid for iOS (major versions run in the mid-teens). Replace >= 26 with the intended iOS major version (e.g. >= 18). [NativeScaffold.ios.kt:45-47]

🤖 Prompt for AI Agents
In
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/NativeScaffold.ios.kt
around lines 45 to 47, the OS version check uses an incorrect threshold
`this.majorVersion >= 26`; update the comparison to the intended iOS major
version (for example `>= 18`) so the blur effect uses the correct iOS version
boundary, i.e., replace `>= 26` with `>= 18` (or the agreed target version).

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26a9bcb and 030e0e9.

📒 Files selected for processing (1)
  • shared/core/src/androidUnitTest/kotlin/konsistTest/android/compose/ComposeTest.kt (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run tests
  • GitHub Check: Lint & Build
🔇 Additional comments (1)
shared/core/src/androidUnitTest/kotlin/konsistTest/android/compose/ComposeTest.kt (1)

33-33: Verify Konsist API supports hasActualModifier
Ensure that hasActualModifier is available in the Konsist version used—if it’s missing, upgrade Konsist or adjust this logic accordingly.

package konsistTest.android.compose

import com.lemonappdev.konsist.api.Konsist
import com.lemonappdev.konsist.api.declaration.KoAnnotationDeclaration

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unused import.

The KoAnnotationDeclaration import appears unused. Consider removing it to keep the imports clean.

Apply this diff:

-import com.lemonappdev.konsist.api.declaration.KoAnnotationDeclaration
📝 Committable suggestion

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

Suggested change
import com.lemonappdev.konsist.api.declaration.KoAnnotationDeclaration
🤖 Prompt for AI Agents
In
shared/core/src/androidUnitTest/kotlin/konsistTest/android/compose/ComposeTest.kt
around line 4, the import
"com.lemonappdev.konsist.api.declaration.KoAnnotationDeclaration" is unused;
remove that import line so the file only contains necessary imports and the
compilation/lint warnings are resolved.

@JuliaJakubcova JuliaJakubcova force-pushed the feature/kmm/cmp-liquid-glass branch from 030e0e9 to 3c45aeb Compare October 9, 2025 13:50

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 030e0e9 and 3c45aeb.

📒 Files selected for processing (1)
  • shared/core/src/androidUnitTest/kotlin/konsistTest/android/compose/ComposeTest.kt (1 hunks)

.let { params ->
params.size == 1 &&
params.all { param -> param.hasDefaultValue() && param.name == "modifier" }
params.all { param -> (param.hasDefaultValue() || fn.hasActualModifier) && param.name == "modifier" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that fn.hasActualModifier is a valid Konsist API property.

The validation logic now permits actual implementations to omit default values for modifier parameters. This aligns with expect/actual patterns in Kotlin Multiplatform (where expect declares the default and actual need not repeat it). However, ensure that hasActualModifier is a valid property in the Konsist API you're using.

Run the following script to verify:

Additionally, search the web to confirm this property exists in the Konsist API version specified in your project dependencies:


🏁 Script executed:

#!/bin/bash
# Description: Verify hasActualModifier exists in Konsist API

# Search for hasActualModifier usage or definition in dependencies
rg -n 'hasActualModifier' --type=kotlin -C3

Length of output: 903


🌐 Web query:

Does the Konsist API provide a hasActualModifier property or method on function declarations?

💡 Result:

Short answer: No — Konsist's API does not expose a hasActualModifier on function declarations. The KoFunctionDeclaration/modifier APIs documented (e.g., Verify Functions / KoFunctionDeclaration pages) list specific modifier checks (hasInternalModifier, hasPublicModifier behavior, etc.) but do not include a hasActualModifier. [1][2]

Sources:

  • KoFunctionDeclaration / Konsist API docs. [1]
  • Konsist "Compiler Type Inference" / Verify Functions documentation (shows available modifier checks and examples). [2]

Replace unsupported fn.hasActualModifier with a valid Konsist modifier check

hasActualModifier isn’t provided by Konsist. Use fn.hasModifier(KoModifier.ACTUAL) (or fn.hasModifier("actual")) to detect the actual modifier:

params.all { param ->
    (param.hasDefaultValue() || fn.hasModifier(KoModifier.ACTUAL)) &&
    param.name == "modifier"
}
🤖 Prompt for AI Agents
In
shared/core/src/androidUnitTest/kotlin/konsistTest/android/compose/ComposeTest.kt
around line 32, the code uses the nonexistent property fn.hasActualModifier;
replace it with the Konsist modifier check fn.hasModifier(KoModifier.ACTUAL) (or
fn.hasModifier("actual")) so the predicate becomes params.all { param ->
(param.hasDefaultValue() || fn.hasModifier(KoModifier.ACTUAL)) && param.name ==
"modifier" }, ensuring you import KoModifier if needed.

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

♻️ Duplicate comments (4)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (2)

26-33: Add KDoc documentation.

This public composable still lacks documentation explaining its purpose and parameters, as noted in the previous review.


50-50: Use theme color instead of hardcoded white.

Line 50 still uses Color.White for the haze container color, which was flagged in the previous review. This prevents proper dark mode support.

ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (2)

26-26: Fix the grammatical error.

The text uses "it's" (contraction of "it is") but should use "its" (possessive) to indicate the item's body.


68-70: Consider implementing the fallback or removing the version check.

The fallback branch for iOS versions earlier than 16.0 is empty. If this test screen is intended only for iOS 16.0+, consider removing the availability check or marking the entire view with @available(iOS 16.0, *). Otherwise, add a placeholder view to provide feedback for unsupported versions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c45aeb and 2eedaa8.

📒 Files selected for processing (2)
  • ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (1 hunks)
  • shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift (2)
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldView.swift (1)
  • iOS (58-86)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/vm/SampleTabBarViewModel.kt (2)
  • index (34-37)
  • index (36-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Lint & Build
  • GitHub Check: Run tests
🔇 Additional comments (1)
shared/sampletabbar/src/commonMain/kotlin/kmp/shared/sampletabbar/presentation/ui/BlurredContainer.kt (1)

34-87: Implementation structure looks good overall.

The blur effect setup, animated visibility, and layout structure are correctly implemented. The bottom gradient properly uses theme colors. Once the hardcoded white color is addressed, this component will properly support theming.

Comment on lines +60 to +65
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Second", systemImage: "house") {

}
.tint(.yellow)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using distinct button labels.

While the exact duplicate toolbar item has been fixed (different icons now), both trailing buttons share the same label "Second" with different icons ("person" vs "house"). This could be confusing for users trying to distinguish between the two buttons. If this is intentional for testing purposes, consider adding a comment to clarify.

Apply this diff to use distinct labels:

                    ToolbarItemGroup(placement: .topBarTrailing) {
-                        Button("Second", systemImage: "house") {
+                        Button("Third", systemImage: "house") {
📝 Committable suggestion

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

Suggested change
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Second", systemImage: "house") {
}
.tint(.yellow)
}
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Third", systemImage: "house") {
}
.tint(.yellow)
}
🤖 Prompt for AI Agents
In
ios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/TestScreen.swift
around lines 60 to 65, both trailing toolbar buttons use the same label "Second"
which is confusing; change the second button's label to a distinct, descriptive
string (e.g., "Profile" or "Home") to match its icon, or if the duplicate label
is intentional for tests, add an inline comment clarifying that intent; update
the label text only and keep the existing systemImage and tint unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant