diff --git a/adapt/src/commonMain/kotlin/design/adapt/AdaptSwitch.kt b/adapt/src/commonMain/kotlin/design/adapt/AdaptSwitch.kt new file mode 100644 index 0000000..b23780b --- /dev/null +++ b/adapt/src/commonMain/kotlin/design/adapt/AdaptSwitch.kt @@ -0,0 +1,159 @@ +/* + * Copyright 2023 Shubham Singh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package design.adapt + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchColors +import androidx.compose.material3.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Shape +import design.adapt.cupertino.IOSToggle +import design.adapt.cupertino.IOSToggleColors +import design.adapt.cupertino.IOSToggleDefaults +import design.adapt.cupertino.MacOSSwitch +import design.adapt.cupertino.MacOSSwitchColors +import design.adapt.cupertino.MacOSSwitchDefaults +import design.adapt.windows.WindowsToggleSwitch +import design.adapt.windows.WindowsToggleSwitchColors +import design.adapt.windows.WindowsToggleSwitchDefaults + +@Composable +fun AdaptSwitch( + checked: Boolean, + onCheckedChange: (checked: Boolean) -> Unit, + modifier: AdaptModifier = AdaptModifier(), + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + configuration: AdaptSwitchConfiguration = AdaptSwitchDefaults.configuration(), +) { + when (LocalPlatform.current) { + // TODO: Separate Web from Android here + Platform.Android, Platform.Web -> Switch( + checked = checked, + colors = configuration.android.colors, + enabled = enabled, + interactionSource = interactionSource, + modifier = modifier.android, + onCheckedChange = onCheckedChange, + thumbContent = configuration.android.thumbContent, + ) + + Platform.IOS -> IOSToggle( + checked = checked, + colors = configuration.iOS.colors, + interactionSource = interactionSource, + modifier = modifier.iOS, + onCheckedChange = onCheckedChange, + thumbShape = configuration.iOS.thumbShape, + trackShape = configuration.iOS.trackShape, + ) + + Platform.MacOS -> MacOSSwitch( + checked = checked, + colors = configuration.macOS.colors, + enabled = enabled, + interactionSource = interactionSource, + modifier = modifier.macOS, + onCheckedChange = onCheckedChange, + thumbShape = configuration.macOS.thumbShape, + trackShape = configuration.macOS.trackShape, + ) + + Platform.Windows -> WindowsToggleSwitch( + alignTextToStart = configuration.windows.alignTextToStart, + checked = checked, + colors = configuration.windows.colors, + enabled = enabled, + header = configuration.windows.header, + interactionSource = interactionSource, + modifier = modifier.windows, + onCheckedChange = onCheckedChange, + text = configuration.windows.text, + thumbShape = configuration.windows.thumbShape, + trackShape = configuration.windows.trackShape, + ) + } +} + +object AdaptSwitchDefaults { + @Composable + fun configuration( + android: AndroidSwitchConfiguration = AndroidSwitchConfiguration( + colors = SwitchDefaults.colors(), + thumbContent = null, + ), + iOS: IOSToggleConfiguration = IOSToggleConfiguration( + colors = IOSToggleDefaults.colors(), + thumbShape = IOSToggleDefaults.ThumbShape, + trackShape = IOSToggleDefaults.TrackShape, + ), + macOS: MacOSSwitchConfiguration = MacOSSwitchConfiguration( + colors = MacOSSwitchDefaults.colors(), + thumbShape = MacOSSwitchDefaults.ThumbShape, + trackShape = MacOSSwitchDefaults.TrackShape, + ), + windows: WindowsToggleSwitchConfiguration = WindowsToggleSwitchConfiguration( + alignTextToStart = false, + colors = WindowsToggleSwitchDefaults.colors(), + header = null, + text = null, + thumbShape = WindowsToggleSwitchDefaults.ThumbShape, + trackShape = WindowsToggleSwitchDefaults.TrackShape, + ), + ) = AdaptSwitchConfiguration(android = android, iOS = iOS, macOS = macOS, windows = windows) +} + +@Immutable +data class AdaptSwitchConfiguration( + val android: AndroidSwitchConfiguration, + val iOS: IOSToggleConfiguration, + val macOS: MacOSSwitchConfiguration, + val windows: WindowsToggleSwitchConfiguration, +) + +@Immutable +data class AndroidSwitchConfiguration( + val colors: SwitchColors, + val thumbContent: @Composable (() -> Unit)?, +) + +@Immutable +data class IOSToggleConfiguration( + val colors: IOSToggleColors, + val thumbShape: Shape, + val trackShape: Shape, +) + +@Immutable +data class MacOSSwitchConfiguration( + val colors: MacOSSwitchColors, + val thumbShape: Shape, + val trackShape: Shape, +) + +@Immutable +data class WindowsToggleSwitchConfiguration( + val alignTextToStart: Boolean, + val colors: WindowsToggleSwitchColors, + val header: @Composable (() -> Unit)?, + val text: @Composable (() -> Unit)?, + val thumbShape: Shape, + val trackShape: Shape, +) diff --git a/adapt/src/commonMain/kotlin/design/adapt/cupertino/IOSToggle.kt b/adapt/src/commonMain/kotlin/design/adapt/cupertino/IOSToggle.kt index dbcc212..a205dac 100644 --- a/adapt/src/commonMain/kotlin/design/adapt/cupertino/IOSToggle.kt +++ b/adapt/src/commonMain/kotlin/design/adapt/cupertino/IOSToggle.kt @@ -57,8 +57,8 @@ fun IOSToggle( onCheckedChange: (checked: Boolean) -> Unit, modifier: Modifier = Modifier, colors: IOSToggleColors = IOSToggleDefaults.colors(), - trackShape: Shape = CircleShape, - thumbShape: Shape = CircleShape, + trackShape: Shape = IOSToggleDefaults.TrackShape, + thumbShape: Shape = IOSToggleDefaults.ThumbShape, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { val density = LocalDensity.current @@ -196,6 +196,8 @@ object IOSToggleDefaults { val ExpandedThumbWidth = ThumbWidth * 1.25f val ThumbHeight = 27.dp val ToggleSize = DpSize(51.dp, 31.dp) + val TrackShape = CircleShape + val ThumbShape = CircleShape @Composable fun colors( diff --git a/adapt/src/commonMain/kotlin/design/adapt/cupertino/MacOSSwitch.kt b/adapt/src/commonMain/kotlin/design/adapt/cupertino/MacOSSwitch.kt index 0379241..1dcb2c3 100644 --- a/adapt/src/commonMain/kotlin/design/adapt/cupertino/MacOSSwitch.kt +++ b/adapt/src/commonMain/kotlin/design/adapt/cupertino/MacOSSwitch.kt @@ -55,8 +55,8 @@ fun MacOSSwitch( onCheckedChange: (checked: Boolean) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, - trackShape: Shape = CircleShape, - thumbShape: Shape = CircleShape, + trackShape: Shape = MacOSSwitchDefaults.TrackShape, + thumbShape: Shape = MacOSSwitchDefaults.ThumbShape, colors: MacOSSwitchColors = MacOSSwitchDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { @@ -207,6 +207,8 @@ data class MacOSSwitchColors( object MacOSSwitchDefaults { val SwitchSize = DpSize(width = 26.dp, height = 15.dp) val ThumbSize = DpSize(width = 13.dp, height = 13.dp) + val TrackShape = CircleShape + val ThumbShape = CircleShape @Composable fun colors( diff --git a/adapt/src/commonMain/kotlin/design/adapt/windows/WindowsToggleSwitch.kt b/adapt/src/commonMain/kotlin/design/adapt/windows/WindowsToggleSwitch.kt index f362e23..2fa87eb 100644 --- a/adapt/src/commonMain/kotlin/design/adapt/windows/WindowsToggleSwitch.kt +++ b/adapt/src/commonMain/kotlin/design/adapt/windows/WindowsToggleSwitch.kt @@ -70,8 +70,8 @@ fun WindowsToggleSwitch( text: @Composable (() -> Unit)? = null, alignTextToStart: Boolean = false, enabled: Boolean = true, - trackShape: Shape = CircleShape, - thumbShape: Shape = CircleShape, + trackShape: Shape = WindowsToggleSwitchDefaults.TrackShape, + thumbShape: Shape = WindowsToggleSwitchDefaults.ThumbShape, colors: WindowsToggleSwitchColors = WindowsToggleSwitchDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { @@ -127,18 +127,18 @@ fun WindowsToggleSwitch( ) val animatedThumbWidth by animateDpAsState( - targetValue = if (isPressed || isDragged) { - WindowsToggleSwitchDefaults.ThumbExtendedSize.width - } else { - WindowsToggleSwitchDefaults.ThumbSize.width + targetValue = when { + isPressed || isDragged -> WindowsToggleSwitchDefaults.ThumbPressedSize + isHovered -> WindowsToggleSwitchDefaults.ThumbHoveredSize + else -> WindowsToggleSwitchDefaults.ThumbSize } ) val animatedThumbHeight by animateDpAsState( - targetValue = if (isPressed || isDragged) { - WindowsToggleSwitchDefaults.ThumbExtendedSize.height + targetValue = if (isPressed || isDragged || isHovered) { + WindowsToggleSwitchDefaults.ThumbHoveredSize } else { - WindowsToggleSwitchDefaults.ThumbSize.height + WindowsToggleSwitchDefaults.ThumbSize } ) @@ -365,9 +365,12 @@ data class WindowsToggleSwitchColors( ) object WindowsToggleSwitchDefaults { - val ThumbSize = DpSize(width = 12.dp, height = 12.dp) - val ThumbExtendedSize = DpSize(width = 17.dp, height = 14.dp) + val ThumbSize = 12.dp + val ThumbHoveredSize = 14.dp + val ThumbPressedSize = 17.dp val ToggleSize = DpSize(width = 38.dp, height = 18.dp) + val ThumbShape = CircleShape + val TrackShape = CircleShape @Composable fun colors( diff --git a/sample/composeApp/src/commonMain/kotlin/design/adapt/App.kt b/sample/composeApp/src/commonMain/kotlin/design/adapt/App.kt index 100b47a..2f44c9c 100644 --- a/sample/composeApp/src/commonMain/kotlin/design/adapt/App.kt +++ b/sample/composeApp/src/commonMain/kotlin/design/adapt/App.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState @@ -39,9 +40,14 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowForward import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.IconButton +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -52,16 +58,19 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import design.adapt.cupertino.CupertinoSpinner import design.adapt.cupertino.IOSButton +import design.adapt.cupertino.IOSToggle import design.adapt.cupertino.MacOSButton +import design.adapt.cupertino.MacOSSwitch import design.adapt.windows.WindowsButton import design.adapt.windows.WindowsProgressRing +import design.adapt.windows.WindowsToggleSwitch import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable internal fun App() { val coroutineScope = rememberCoroutineScope() - val horizontalPagerState = rememberPagerState { 2 } + val horizontalPagerState = rememberPagerState { 3 } AdaptTheme { Box( @@ -121,10 +130,14 @@ internal fun App() { } ) } - HorizontalPager(state = horizontalPagerState) { page -> + HorizontalPager( + state = horizontalPagerState, + beyondBoundsPageCount = 1, + ) { page -> when (page) { 0 -> ButtonsDemoPage() - else -> IndicatorsDemoPage() + 1 -> IndicatorsDemoPage() + else -> SwitchesDemoPage() } } } @@ -230,3 +243,66 @@ fun IndicatorsDemoPage() { } } } + +@Composable +fun SwitchesDemoPage() { + var checked by remember { mutableStateOf(true) } + + ColumnScaffold(title = "Switches") { + AdaptText(text = "This switch should look native-like on all platforms") + SpacedRow { + AdaptSwitch(checked = checked, onCheckedChange = { checked = it }) + AdaptSwitch(checked = checked, onCheckedChange = { checked = it }, enabled = false) + } + AdaptText(text = "This switch should use the Material design system on all platforms") + SpacedRow { + Switch(checked = checked, onCheckedChange = { checked = it }) + Switch(checked = checked, onCheckedChange = { checked = it }, enabled = false) + } + AdaptText( + text = "This switch should use iOS' variant of the Cupertino design system on all " + + "platforms" + ) + SpacedRow { + IOSToggle(checked = checked, onCheckedChange = { checked = it }) + } + AdaptText( + text = "This switch should use macOS' variant of the Cupertino design system on all " + + "platforms" + ) + SpacedRow { + MacOSSwitch(checked = checked, onCheckedChange = { checked = it }) + MacOSSwitch(checked = checked, onCheckedChange = { checked = it }, enabled = false) + } + AdaptText(text = "This switch should use the WinUI design system on all platforms") + SpacedRow { + WindowsToggleSwitch(checked = checked, onCheckedChange = { checked = it }) + WindowsToggleSwitch( + checked = checked, + onCheckedChange = { checked = it }, + enabled = false + ) + WindowsToggleSwitch( + checked = checked, + onCheckedChange = { checked = it }, + text = { + AdaptText( + modifier = Modifier.width(20.dp), + text = if (checked) "On" else "Off" + ) + }, + ) + WindowsToggleSwitch( + checked = checked, + onCheckedChange = { checked = it }, + text = { + AdaptText( + modifier = Modifier.width(20.dp), + text = if (checked) "On" else "Off" + ) + }, + header = { AdaptText(text = "Header") } + ) + } + } +}