💄 [KMM] Liquid glass tab bar in compose multiplatform#43
💄 [KMM] Liquid glass tab bar in compose multiplatform#43JuliaJakubcova wants to merge 9 commits into
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds a new shared module Changes
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Actionable comments posted: 8
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (3)
shared/base/src/commonMain/moko-resources/images/home.svgis excluded by!**/*.svgshared/base/src/commonMain/moko-resources/images/person.svgis excluded by!**/*.svgshared/base/src/commonMain/moko-resources/images/search.svgis 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
InttoKotlinIntand 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 = falseis 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
UIViewControllerfor content, complementing the commonBottomBarTabthat 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.
There was a problem hiding this comment.
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 usingrememberandrememberUpdatedState.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
📒 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 = falseis 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
UIViewControllerfor 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
toIosTabextension function provides a clean conversion between common and iOS-specific tab representations, properly wrapping Compose content in a UIViewController with the necessary composition locals.
There was a problem hiding this comment.
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: MovesetContenttoonCreate(don’t recreate composition inonStart)Calling
setContentinonStartresets the composition on every start/resume, losing state and wasting work. Initialize once inonCreate.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
📒 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
firstOrNullhandles 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
didSetguard 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: LGTMInterface is minimal and clear, matches expected responsibilities.
shared/sampletabbar/src/iosMain/kotlin/kmp/shared/sampletabbar/presentation/ui/ComposeViewFactory.kt (1)
6-15: LGTMActual interface matches the platform contract and pairs nicely with the Swift implementation.
shared/sampletabbar/build.gradle.kts (1)
19-39: Ignore custom source-set verificationcommonMainImplementation, 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 neededorg.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.
| // return UIHostingController(rootView: TestScreen()) | ||
| // let main = UITabBarController() | ||
| // main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()] | ||
| // return main |
There was a problem hiding this comment.
🧹 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.
| // 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).
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| 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).
| if #available(iOS 26.0, *) { | ||
| ComposeViewController { observable.content(tab.position) } | ||
| .ignoresSafeArea() | ||
| } else { | ||
| ComposeViewController { observable.content(tab.position) } | ||
| } | ||
| } |
There was a problem hiding this comment.
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().
| // ScrollView { | ||
| // ForEach((0...20), id: \.self) { _ in | ||
| // Text("doiaw jmo dcposekoif eswop[ijj fe ss") | ||
| // Color.red | ||
| // .frame(height: 100) | ||
| // } | ||
| // } |
There was a problem hiding this comment.
🧹 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.
| // 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", |
There was a problem hiding this comment.
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.
| 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.
| override suspend fun applyIntent(intent: SampleTabBarIntent) { | ||
| when (intent) { | ||
| SampleTabBarIntent.OnAppeared -> onAppeared() | ||
| is SampleTabBarIntent.OnTabSelected -> update { copy(selectedTabPosition = intent.index) } | ||
| } |
There was a problem hiding this comment.
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.
| 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 | |||
There was a problem hiding this comment.
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.
| 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.
| BlurredContainer( | ||
| top = WindowInsets.safeContent.asPaddingValues().calculateTopPadding(), | ||
| bottom = WindowInsets.safeContent.asPaddingValues().calculateBottomPadding(), | ||
| modifier = Modifier.fillMaxSize(), | ||
| ) { | ||
| content(position, WindowInsets.safeContent.asPaddingValues()) | ||
| } |
There was a problem hiding this comment.
🧹 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.
| remember(toolbar) { delegate.updateToolbar(toolbar) } | ||
| remember(tabs) { delegate.updateTabs(tabs) } | ||
| remember(selectedTabPosition) { delegate.updateSelectedTab(selectedTabPosition) } |
There was a problem hiding this comment.
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.
b1b00f5 to
16ede13
Compare
There was a problem hiding this comment.
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
bottompadding to eachNavigationBarItemmakes items individually taller rather than offsetting the entire bar. Material3'sNavigationBartypically handles window insets automatically. ApplywindowInsetsPadding(WindowInsets.systemBars)to theNavigationBarcontainer (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 useflowController.
flowControlleris 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.uiToolingPreviewis 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 factoryfinal(duplicate from prior review).No subclassing expected;
finaltightens 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: Initializetoolbarin 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
scrollBehavioris created but never connected to theScaffold. Without thenestedScrollmodifier, 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.nestedScrollshared/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.CompositionLocalProvidershared/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.Whiteas the container color, which doesn't respect the Material theme and will look incorrect in dark mode. Line 43 correctly usesMaterialTheme.colorScheme.backgroundfor 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
ExperimentalHazeMaterialsApiimport 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.ImageResourceAnd 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.selectedTabPositioncan 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
OnTabSelectedintent directly updatesselectedTabPositionwith the provided index without validation. If an invalid index is passed, this will cause crashes in UI code that directly accessesSampleTab.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 ofcurrentTabPositionparameter.The
contentlambda receivescurrentTabPosition: Int?as nullable, but the reason for this nullability isn't documented. From the implementations, whentabs.isEmpty(),currentTabPositionwill benull.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.
ToolbarButtonDatacontains anonClicklambda, 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 redundantpositionfield fromTabItem.Since
tabsis passed as aList<TabItem>, the position is already implicitly defined by the list index. Maintaining an explicitpositionfield 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)orforEachIndexed).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 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 eachBottomBarTabForIosinstance defines a uniqueposition, 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 >= 26will never trigger; replace with the intended minimum iOS major version (e.g. ≥17).
| let main = UITabBarController() | ||
| main.viewControllers = [setupSampleTab(), setupSampleSharedViewModelTab(), setupSampleComposeMultiplatformTab(), setupSampleComposeNavigationTab()] | ||
| return main | ||
| @Injected(\.sampleTabBarViewModel) var viewModel: KMPShared.SampleTabBarViewModel |
There was a problem hiding this comment.
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.
| @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 { |
There was a problem hiding this comment.
🧹 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.
| 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.
| func updateTabs(tabs: [TabItem]) { | ||
| self.tabs = tabs | ||
| } | ||
|
|
||
| func updateSelectedTab(selectedTabPosition: Int32) { | ||
| self.selectedTab = selectedTabPosition | ||
| } |
There was a problem hiding this comment.
🧹 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.
| 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).
| 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 | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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 | |
| } | |
| } | |
| } |
| } else { | ||
| // Fallback on earlier versions | ||
| } |
There was a problem hiding this comment.
🧹 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)
}| val selectedTab = tabs.firstOrNull { it.position == selectedTabPosition } | ||
| Column(modifier = modifier) { | ||
| Box(modifier = Modifier.weight(1f)) { | ||
| selectedTab?.content?.invoke() |
There was a problem hiding this comment.
🧹 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.
| 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.
| if (position != null) { | ||
| val tab = SampleTab.entries[position] |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| val shapes = Shapes( | ||
| small = RoundedCornerShape(Values.Radius.large), | ||
| medium = RoundedCornerShape(Values.Radius.medium), | ||
| large = RoundedCornerShape(Values.Radius.small), | ||
| ) |
There was a problem hiding this comment.
🛠️ 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 kotlinLength 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 kotlinLength 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.ktLength 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.
| 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.
16ede13 to
cb2afed
Compare
There was a problem hiding this comment.
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
ifstatement and theelseclause.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) orMaterialTheme.colorScheme.surfaceto 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/largeare still swapped. Map each size to its matchingValues.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
rememberto trigger delegate updates is an anti-pattern. UseLaunchedEffectkeyed 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
viewModelis injected but never used. Either pass it toSampleTabBarViewor 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 mainshared/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.uiToolingPreviewis 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 setsios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/SwiftUIFactory.swift (1)
9-12: Seal the factory withfinal.No subclassing is intended; marking the class
finalprevents 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 unusedflowControllerparameter.The
flowControlleris 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
flowControllerin 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.CompositionLocalProviderios/PresentationLayer/SampleComposeMultiplatform/Sources/SampleComposeMultiplatform/PlatformSpecificView/NativeScaffoldObservable.swift (3)
24-35: Critical: Missing toolbar initialization.The
toolbarproperty 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 withfinal.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: clampselectedTabwhen tabs change.If the tabs array shrinks,
selectedTabcan 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
OnTabSelectedintent updatesselectedTabPositiondirectly without validating thatintent.indexis within the valid range oftabs. This can lead to out-of-bounds exceptions in UI code that directly indexesSampleTab.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
ExperimentalHazeMaterialsApiimport is unused in this file; the iOS actual implementation handles haze effects.Apply this diff:
-import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApiAlso 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
selectedTabPositionis 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
IndexOutOfBoundsExceptionifpositionis 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 whentoolbarisnil. 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 ofcurrentTabPosition.The
currentTabPositionparameter in thecontentlambda is nullable, but the function lacks documentation explaining when it will benull(i.e., whentabs.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
onClicklambda in a data class breaks value equality semantics. Two instances with identical data but different lambda references won't be equal, andequals()/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 redundantpositionfield fromTabItem.Since tabs are passed as a
List<TabItem>, the position is implicitly defined by the list index. Storing an explicitpositionfield 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
📒 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
elsebranch 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 | |||
There was a problem hiding this comment.
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.
| 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.
| .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) | ||
| } | ||
| } |
There was a problem hiding this comment.
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
3is hardcoded
Consider:
- Wire button actions to
observablecallbacks or remove the buttons if not needed - Make the navigation title configurable via the observable
- 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.
| .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.
| public extension Color { | ||
| init(kmpColor: KMPShared.NativeColor) { | ||
| self = Color(uiColor: kmpColor.toUIColor()) | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| modifier = Modifier.padding( | ||
| bottom = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding(), | ||
| ), |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| 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(), | ||
| ), |
There was a problem hiding this comment.
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.
| @OptIn(ExperimentalHazeMaterialsApi::class) | ||
| @Composable | ||
| fun BlurredContainer( | ||
| top: Dp = 0.dp, | ||
| bottom: Dp = 0.dp, | ||
| modifier: Modifier = Modifier, | ||
| content: @Composable () -> Unit, | ||
| ) { |
There was a problem hiding this comment.
🧹 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 |
There was a problem hiding this comment.
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.
| val showBlur = NSProcessInfo.processInfo.operatingSystemVersion.useContents { | ||
| this.majorVersion >= 26 | ||
| } |
There was a problem hiding this comment.
🧩 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=ktLength 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).
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 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 supportshasActualModifier
Ensure thathasActualModifieris 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 |
There was a problem hiding this comment.
🧹 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.
| 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.
030e0e9 to
3c45aeb
Compare
| .let { params -> | ||
| params.size == 1 && | ||
| params.all { param -> param.hasDefaultValue() && param.name == "modifier" } | ||
| params.all { param -> (param.hasDefaultValue() || fn.hasActualModifier) && param.name == "modifier" } |
There was a problem hiding this comment.
🧩 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 -C3Length 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.
There was a problem hiding this comment.
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.Whitefor 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
📒 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.
| ToolbarItemGroup(placement: .topBarTrailing) { | ||
| Button("Second", systemImage: "house") { | ||
|
|
||
| } | ||
| .tint(.yellow) | ||
| } |
There was a problem hiding this comment.
🧹 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.
| 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.
📝 Description
💡 What’s new?
😶 What’s missing?
Summary by CodeRabbit
New Features
iOS
Android
Chores