diff --git a/README.md b/README.md index c7f7c1dd..6eeba21a 100644 --- a/README.md +++ b/README.md @@ -1463,16 +1463,8 @@ Support levels: .backgroundStyle - 🟡 - -
- .badge - -
- + ✅ + .badge 🟢 diff --git a/Sources/SkipUI/SkipUI/Containers/List.swift b/Sources/SkipUI/SkipUI/Containers/List.swift index 9d37af99..eb28086d 100644 --- a/Sources/SkipUI/SkipUI/Containers/List.swift +++ b/Sources/SkipUI/SkipUI/Containers/List.swift @@ -903,30 +903,6 @@ final class ListItemModifier: RenderModifier { return ListItemModifier(background: background, separator: separator) } } - -final class BadgeModifier: RenderModifier { - let badge: Text? - let prominence: BadgeProminence? - - init(badge: Text? = nil, prominence: BadgeProminence? = nil) { - self.badge = badge - self.prominence = prominence - super.init() - } - - static func combined(for renderable: Renderable) -> BadgeModifier { - var badge: Text? = nil - var prominence: BadgeProminence? = nil - renderable.forEachModifier { - if let badgeModifier = $0 as? BadgeModifier { - badge = badge ?? badgeModifier.badge - prominence = prominence ?? badgeModifier.prominence - } - return nil - } - return BadgeModifier(badge: badge, prominence: prominence) - } -} #endif #if false diff --git a/Sources/SkipUI/SkipUI/Containers/TabView.swift b/Sources/SkipUI/SkipUI/Containers/TabView.swift index ab2f7f54..65b07a56 100644 --- a/Sources/SkipUI/SkipUI/Containers/TabView.swift +++ b/Sources/SkipUI/SkipUI/Containers/TabView.swift @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.safeDrawing @@ -39,6 +40,8 @@ import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.NavigationRailItemDefaults +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.contentColorFor import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffoldDefaults @@ -228,10 +231,16 @@ public struct TabView : View, Renderable { let tabRenderables = EvaluateContent(context: tabContext) let tabs: kotlin.collections.List = tabRenderables.map { let renderable = $0 as Renderable // Let transpiler understand type - if let tab = renderable.strip() as? Tab { + let badgeModifier = BadgeModifier.combined(for: renderable) + if var tab = renderable.strip() as? Tab { + if let badge = badgeModifier.badge { + tab.badge = badge + } return tab } else if let tabItemModifier = renderable.forEachModifier(perform: { $0 as? TabItemModifier }) as? TabItemModifier { - return Tab(content: { renderable.asView() }, label: { tabItemModifier.label }) + var tab = Tab(content: { renderable.asView() }, label: { tabItemModifier.label }) + tab.badge = badgeModifier.badge + return tab } else { return nil } @@ -743,6 +752,7 @@ public enum AdaptableTabBarPlacement : Hashable { public protocol TabContent : View { var isHidden: Bool { get set } var isDisabled: Bool { get set } + var badge: Text? { get set } } // SKIP @bridge @@ -850,6 +860,7 @@ public struct Tab : TabContent, Renderable { public var isHidden = false public var isDisabled = false + public var badge: Text? = nil #if SKIP @Composable override func Render(context: ComposeContext) { @@ -888,8 +899,11 @@ public struct Tab : TabContent, Renderable { renderable.Render(context: context) } } - Box(modifier: outerModifier, contentAlignment: androidx.compose.ui.Alignment.Center) { - Box(modifier: Modifier.graphicsLayer(scaleX: Float(1.5), scaleY: Float(1.5)), contentAlignment: androidx.compose.ui.Alignment.Center) { + let renderIcon: (@Composable () -> ()) = { + Box( + modifier: Modifier.graphicsLayer(scaleX: Float(1.5), scaleY: Float(1.5)), + contentAlignment: androidx.compose.ui.Alignment.Center + ) { // Default to a lighter symbol weight so tab icons approximate SwiftUI sizing if EnvironmentValues.shared._textEnvironment.fontWeight == nil { EnvironmentValues.shared.setValues { @@ -905,6 +919,22 @@ public struct Tab : TabContent, Renderable { } } } + + Box(modifier: outerModifier, contentAlignment: androidx.compose.ui.Alignment.Center) { + if let badge { + BadgedBox( + badge: { + Badge(modifier: Modifier.offset(x: 8.dp, y: -4.dp)) { + badge.foregroundStyle(Color.white).Render(context: context) + } + } + ) { + renderIcon() + } + } else { + renderIcon() + } + } } #else public var body: some View { @@ -960,6 +990,7 @@ public struct TabSection : TabContent, Renderable { public var isHidden = false public var isDisabled = false + public var badge: Text? = nil #if SKIP @Composable override func Evaluate(context: ComposeContext, options: Int) -> kotlin.collections.List { @@ -971,6 +1002,9 @@ public struct TabSection : TabContent, Renderable { if isDisabled { (renderable as? TabContent)?.isDisabled = true } + if let badge { + (renderable as? TabContent)?.badge = badge + } } return renderables } @@ -1078,29 +1112,34 @@ extension TabContent { return self } - @available(*, unavailable) public func badge(_ count: Int) -> some TabContent { - return self + var tabContent = self + tabContent.badge = count > 0 ? Text(String(count)) : nil + return tabContent } - @available(*, unavailable) public func badge(_ label: Text?) -> some TabContent { - return self + var tabContent = self + tabContent.badge = label + return tabContent } - @available(*, unavailable) public func badge(_ key: LocalizedStringKey) -> some TabContent { - return self + var tabContent = self + tabContent.badge = Text(key) + return tabContent } - @available(*, unavailable) public func badge(_ resource: LocalizedStringResource) -> some TabContent { - return self + var tabContent = self + tabContent.badge = Text(resource) + return tabContent } - @available(*, unavailable) public func badge(_ label: String) -> some TabContent { - return self + var tabContent = self + tabContent.badge = Text(label) + return tabContent } @available(*, unavailable) diff --git a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift index c4bbcb31..3e69dd90 100644 --- a/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift +++ b/Sources/SkipUI/SkipUI/View/AdditionalViewModifiers.swift @@ -1487,6 +1487,30 @@ final class AspectRatioModifier: RenderModifier { } } +final class BadgeModifier: RenderModifier { + let badge: Text? + let prominence: BadgeProminence? + + init(badge: Text? = nil, prominence: BadgeProminence? = nil) { + self.badge = badge + self.prominence = prominence + super.init() + } + + static func combined(for renderable: Renderable) -> BadgeModifier { + var badge: Text? = nil + var prominence: BadgeProminence? = nil + renderable.forEachModifier { + if let badgeModifier = $0 as? BadgeModifier { + badge = badge ?? badgeModifier.badge + prominence = prominence ?? badgeModifier.prominence + } + return nil + } + return BadgeModifier(badge: badge, prominence: prominence) + } +} + final class DisabledModifier: EnvironmentModifier { let disabled: Bool