Skip to content

Commit

Permalink
Merge pull request #70 from skiptools/material3
Browse files Browse the repository at this point in the history
Material 3 component customization API
  • Loading branch information
aabewhite authored Oct 5, 2024
2 parents 7bc7515 + 67296af commit 28080f1
Show file tree
Hide file tree
Showing 9 changed files with 750 additions and 247 deletions.
583 changes: 365 additions & 218 deletions README.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Sources/SkipUI/SkipUI/Color/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public struct Color: ShapeStyle, Hashable, Sendable {
Box(modifier: modifier)
}

/// Return the equivalent Compose color.
@Composable public func asComposeColor() -> androidx.compose.ui.graphics.Color {
return colorImpl()
}

// MARK: - ShapeStyle

@Composable override func asColor(opacity: Double, animationContext: ComposeContext?) -> androidx.compose.ui.graphics.Color? {
Expand Down
6 changes: 3 additions & 3 deletions Sources/SkipUI/SkipUI/Color/ColorScheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public enum ColorScheme : CaseIterable, Hashable, Sendable {
} else {
colorScheme = isDynamicColor ? dynamicLightColorScheme(context) : lightColorScheme()
}
guard let customization = EnvironmentValues.shared._materialColorScheme else {
guard let customization = EnvironmentValues.shared._material3ColorScheme else {
return colorScheme
}
return customization(colorScheme, isDarkMode)
Expand Down Expand Up @@ -73,7 +73,7 @@ extension View {
}

public func material3ColorScheme(_ scheme: (@Composable (androidx.compose.material3.ColorScheme, Bool) -> androidx.compose.material3.ColorScheme)?) -> some View {
return environment(\._materialColorScheme, scheme)
return environment(\._material3ColorScheme, scheme)
}
#endif
}
Expand All @@ -85,7 +85,7 @@ extension View {

@Composable public func Material3ColorScheme(_ scheme: (@Composable (androidx.compose.material3.ColorScheme, Bool) -> androidx.compose.material3.ColorScheme)?, content: @Composable () -> Void) {
EnvironmentValues.shared.setValues {
$0.set_materialColorScheme(scheme)
$0.set_material3ColorScheme(scheme)
} in: {
content()
}
Expand Down
78 changes: 75 additions & 3 deletions Sources/SkipUI/SkipUI/Containers/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,13 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
Expand Down Expand Up @@ -337,15 +342,27 @@ public struct NavigationStack<Root> : View where Root: View {
let toolbarItemContext = context.content(modifier: Modifier.padding(start: 12.dp, end: 12.dp))
topTrailingItems.forEach { $0.Compose(context: toolbarItemContext) }
}
var options = Material3TopAppBarOptions(title: topBarTitle, modifier: topBarModifier, navigationIcon: topBarNavigationIcon, colors: topBarColors, scrollBehavior: scrollBehavior)
if let updateOptions = EnvironmentValues.shared._material3TopAppBar {
options = updateOptions(options)
}
if isInlineTitleDisplayMode {
TopAppBar(modifier: topBarModifier, colors: topBarColors, title: topBarTitle, navigationIcon: topBarNavigationIcon, actions: { topBarActions() }, scrollBehavior: scrollBehavior)
if options.preferCenterAlignedStyle {
CenterAlignedTopAppBar(title: options.title, modifier: options.modifier, navigationIcon: options.navigationIcon, actions: { topBarActions() }, colors: options.colors, scrollBehavior: options.scrollBehavior)
} else {
TopAppBar(title: options.title, modifier: options.modifier, navigationIcon: options.navigationIcon, actions: { topBarActions() }, colors: options.colors, scrollBehavior: options.scrollBehavior)
}
} else {
// Force a larger, bold title style in the uncollapsed state by replacing the headlineSmall style the bar uses
let typography = MaterialTheme.typography
let appBarTitleStyle = typography.headlineLarge.copy(fontWeight: FontWeight.Bold)
let appBarTypography = typography.copy(headlineSmall: appBarTitleStyle)
MaterialTheme(colorScheme: MaterialTheme.colorScheme, typography: appBarTypography, shapes: MaterialTheme.shapes) {
MediumTopAppBar(modifier: topBarModifier, colors: topBarColors, title: topBarTitle, navigationIcon: topBarNavigationIcon, actions: { topBarActions() }, scrollBehavior: scrollBehavior)
if options.preferLargeStyle {
LargeTopAppBar(title: options.title, modifier: options.modifier, navigationIcon: options.navigationIcon, actions: { topBarActions() }, colors: options.colors, scrollBehavior: options.scrollBehavior)
} else {
MediumTopAppBar(title: options.title, modifier: options.modifier, navigationIcon: options.navigationIcon, actions: { topBarActions() }, colors: options.colors, scrollBehavior: options.scrollBehavior)
}
}
}
}
Expand Down Expand Up @@ -427,7 +444,11 @@ public struct NavigationStack<Root> : View where Root: View {
PaddingLayout(padding: EdgeInsets(top: 0.0, leading: 0.0, bottom: Double(-bottomPadding.value), trailing: 0.0), context: context.content()) { context in
let containerColor = canScrollForward ? bottomBarBackgroundColor : unscrolledBottomBarBackgroundColor
let windowInsets = EnvironmentValues.shared._isEdgeToEdge == true ? BottomAppBarDefaults.windowInsets : WindowInsets(bottom: 0.dp)
BottomAppBar(modifier: context.modifier.then(bottomBarModifier), containerColor: containerColor, contentPadding: PaddingValues.Absolute(left: 16.dp, right: 16.dp), windowInsets: windowInsets) {
var options = Material3BottomAppBarOptions(modifier: context.modifier.then(bottomBarModifier), containerColor: containerColor, contentColor: MaterialTheme.colorScheme.contentColorFor(containerColor), contentPadding: PaddingValues.Absolute(left: 16.dp, right: 16.dp))
if let updateOptions = EnvironmentValues.shared._material3BottomAppBar {
options = updateOptions(options)
}
BottomAppBar(modifier: options.modifier, containerColor: options.containerColor, contentColor: options.contentColor, tonalElevation: options.tonalElevation, contentPadding: options.contentPadding, windowInsets: windowInsets) {
// Use an HStack so that it sets up the environment for bottom toolbar Spacers
HStack(spacing: 24.0) {
ComposeBuilder { itemContext in
Expand Down Expand Up @@ -910,9 +931,60 @@ extension View {
public func navigationTitle(_ title: Binding<String>) -> some View {
return self
}

#if SKIP
public func material3TopAppBar(_ options: @Composable (Material3TopAppBarOptions) -> Material3TopAppBarOptions) -> View {
return environment(\._material3TopAppBar, options)
}

public func material3BottomAppBar(_ options: @Composable (Material3BottomAppBarOptions) -> Material3BottomAppBarOptions) -> View {
return environment(\._material3BottomAppBar, options)
}
#endif
}

#if SKIP
// SKIP INSERT: @OptIn(ExperimentalMaterial3Api::class)
public struct Material3TopAppBarOptions {
public var title: @Composable () -> Void
public var modifier: Modifier = Modifier
public var navigationIcon: @Composable () -> Void = {}
public var colors: TopAppBarColors
public var scrollBehavior: TopAppBarScrollBehavior? = nil
public var preferCenterAlignedStyle = false
public var preferLargeStyle = false

public func copy(
title: @Composable () -> Void = self.title,
modifier: Modifier = self.modifier,
navigationIcon: @Composable () -> Void = self.navigationIcon,
colors: TopAppBarColors = self.colors,
scrollBehavior: TopAppBarScrollBehavior? = self.scrollBehavior,
preferCenterAlignedStyle: Bool = self.preferCenterAlignedStyle,
preferLargeStyle: Bool = self.preferLargeStyle
) -> Material3TopAppBarOptions {
return Material3TopAppBarOptions(title: title, modifier: modifier, navigationIcon: navigationIcon, colors: colors, scrollBehavior: scrollBehavior, preferCenterAlignedStyle: preferCenterAlignedStyle, preferLargeStyle: preferLargeStyle)
}
}

public struct Material3BottomAppBarOptions {
public var modifier: Modifier = Modifier
public var containerColor: androidx.compose.ui.graphics.Color
public var contentColor: androidx.compose.ui.graphics.Color
public var tonalElevation: Dp = BottomAppBarDefaults.ContainerElevation
public var contentPadding: PaddingValues = BottomAppBarDefaults.ContentPadding

public func copy(
modifier: Modifier = self.modifier,
containerColor: androidx.compose.ui.graphics.Color = self.containerColor,
contentColor: androidx.compose.ui.graphics.Color = self.contentColor,
tonalElevation: Dp = self.tonalElevation,
contentPadding: PaddingValues = self.contentPadding
) -> Material3BottomAppBarOptions {
return Material3BottomAppBarOptions(modifier: modifier, containerColor: containerColor, contentColor: contentColor, tonalElevation: tonalElevation, contentPadding: contentPadding)
}
}

struct NavigationDestinationsPreferenceKey: PreferenceKey {
typealias Value = NavigationDestinations

Expand Down
96 changes: 82 additions & 14 deletions Sources/SkipUI/SkipUI/Containers/TabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -24,15 +25,18 @@ import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemColors
import androidx.compose.material3.NavigationBarItemDefaults
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
Expand All @@ -42,6 +46,7 @@ import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
Expand Down Expand Up @@ -168,23 +173,46 @@ public struct TabView : View {
// Pull the tab bar below the keyboard
let bottomPadding = with(density) { min(bottomBarHeightPx.value, Float(WindowInsets.ime.getBottom(density))).toDp() }
PaddingLayout(padding: EdgeInsets(top: 0.0, leading: 0.0, bottom: Double(-bottomPadding.value), trailing: 0.0), context: context.content()) { context in
let tabItemsState = rememberUpdatedState(tabItems)
let containerColor = canScrollForward ? tabBarBackgroundColor : unscrolledTabBarBackgroundColor
NavigationBar(modifier: context.modifier.then(tabBarModifier), containerColor: containerColor) {
let onItemClick: (Int) -> Void = { tabIndex in
let route = String(describing: tabIndex)
if let selection, let tagValue = tagValue(route: route, in: tabViews) {
selection.wrappedValue = tagValue
} else {
navigate(controller: navController, route: route)
}
}
let itemIcon: @Composable (Int) -> Void = { tabIndex in
let tabItem = tabItemsState.value[tabIndex]
tabItem?.ComposeImage(context: tabItemContext)
}
let itemLabel: @Composable (Int) -> Void = { tabIndex in
let tabItem = tabItemsState.value[tabIndex]
tabItem?.ComposeTitle(context: tabItemContext)
}
var options = Material3NavigationBarOptions(modifier: context.modifier.then(tabBarModifier), containerColor: containerColor, contentColor: MaterialTheme.colorScheme.contentColorFor(containerColor), onItemClick: onItemClick, itemIcon: itemIcon, itemLabel: itemLabel, itemColors: tabBarItemColors)
if let updateOptions = EnvironmentValues.shared._material3NavigationBar {
options = updateOptions(options)
}
NavigationBar(modifier: options.modifier, containerColor: options.containerColor, contentColor: options.contentColor, tonalElevation: options.tonalElevation) {
for tabIndex in 0..<tabViews.count {
let route = String(describing: tabIndex)
let tabItem = tabItems[tabIndex]
NavigationBarItem(
colors: tabBarItemColors,
icon: { tabItem?.ComposeImage(context: tabItemContext) },
label: { tabItem?.ComposeTitle(context: tabItemContext) },
selected: route == currentRoute,
onClick: {
if let selection, let tagValue = tagValue(route: route, in: tabViews) {
selection.wrappedValue = tagValue
} else {
navigate(controller: navController, route: route)
}
}
let label: (@Composable () -> Void)?
if let itemLabel = options.itemLabel {
label = { itemLabel(tabIndex) }
} else {
label = nil
}
NavigationBarItem(selected: route == currentRoute,
onClick: { options.onItemClick(tabIndex) },
icon: { options.itemIcon(tabIndex) },
modifier: options.itemModifier(tabIndex),
enabled: options.itemEnabled(tabIndex),
label: label,
alwaysShowLabel: options.alwaysShowItemLabels,
colors: options.itemColors,
interactionSource: options.itemInteractionSource
)
}
}
Expand Down Expand Up @@ -414,7 +442,47 @@ extension View {
// We only support .automatic
return self
}

#if SKIP
public func material3NavigationBar(_ options: @Composable (Material3NavigationBarOptions) -> Material3NavigationBarOptions) -> View {
return environment(\._material3NavigationBar, options)
}
#endif
}

#if SKIP
public struct Material3NavigationBarOptions {
public var modifier: Modifier = Modifier
public var containerColor: androidx.compose.ui.graphics.Color
public var contentColor: androidx.compose.ui.graphics.Color
public var tonalElevation: Dp = NavigationBarDefaults.Elevation
public var onItemClick: (Int) -> Void
public var itemIcon: @Composable (Int) -> Void
public var itemModifier: @Composable (Int) -> Modifier = { _ in Modifier }
public var itemEnabled: (Int) -> Boolean = { _ in true }
public var itemLabel: (@Composable (Int) -> Void)? = nil
public var alwaysShowItemLabels = true
public var itemColors: NavigationBarItemColors
public var itemInteractionSource: MutableInteractionSource? = nil

public func copy(
modifier: Modifier = self.modifier,
containerColor: androidx.compose.ui.graphics.Color = self.containerColor,
contentColor: androidx.compose.ui.graphics.Color = self.contentColor,
tonalElevation: Dp = self.tonalElevation,
onItemClick: (Int) -> Void = self.onItemClick,
itemIcon: @Composable (Int) -> Void = self.itemIcon,
itemModifier: @Composable (Int) -> Modifier = self.itemModifier,
itemEnabled: (Int) -> Boolean = self.itemEnabled,
itemLabel: (@Composable (Int) -> Void)? = self.itemLabel,
alwaysShowItemLabels: Bool = self.alwaysShowItemLabels,
itemColors: NavigationBarItemColors = self.itemColors,
itemInteractionSource: MutableInteractionSource? = self.itemInteractionSource
) -> Material3NavigationBarOptions {
return Material3NavigationBarOptions(modifier: modifier, containerColor: containerColor, contentColor: contentColor, tonalElevation: tonalElevation, onItemClick: onItemClick, itemIcon: itemIcon, itemModifier: itemModifier, itemEnabled: itemEnabled, itemLabel: itemLabel, alwaysShowItemLabels: alwaysShowItemLabels, itemColors: itemColors, itemInteractionSource: itemInteractionSource)
}
}
#endif

#if false

Expand Down
Loading

0 comments on commit 28080f1

Please sign in to comment.