Skip to content

Commit

Permalink
Implement AdaptCircularIndicator and fix WindowsButton's interaction …
Browse files Browse the repository at this point in the history
…behavior
  • Loading branch information
shubhamsinghshubham777 committed Apr 14, 2024
1 parent 9548496 commit 83c55be
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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.previews

import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import design.adapt.AdaptCircularIndicator
import design.adapt.Platform

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun AndroidLightPreview() {
IndicatorPreview(platform = Platform.Android)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AndroidDarkPreview() {
IndicatorPreview(platform = Platform.Android)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun IOSLightPreview() {
IndicatorPreview(platform = Platform.IOS)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun IOSDarkPreview() {
IndicatorPreview(platform = Platform.IOS)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun MacOSLightPreview() {
IndicatorPreview(platform = Platform.MacOS)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun MacOSDarkPreview() {
IndicatorPreview(platform = Platform.MacOS)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Composable
private fun WindowsLightPreview() {
IndicatorPreview(platform = Platform.Windows)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WindowsDarkPreview() {
IndicatorPreview(platform = Platform.Windows)
}

@Composable
private fun IndicatorPreview(platform: Platform) {
AdaptPreviewsTheme(platform = platform) {
Column(
modifier = Modifier
.background(Color.White.copy(alpha = 0.6f))
.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
AdaptCircularIndicator()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private fun ProgressRingPreview(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
WindowsProgressRing(trackColor = WindowsProgressRingDefaults.TrackColor)
WindowsProgressRing(progress = 0.6f, trackColor = WindowsProgressRingDefaults.TrackColor)
WindowsProgressRing(progress = { 0.6f }, trackColor = WindowsProgressRingDefaults.TrackColor)
}
}
}
146 changes: 146 additions & 0 deletions adapt/src/commonMain/kotlin/design/adapt/AdaptCircularIndicator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* 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.material3.CircularProgressIndicator
import androidx.compose.material3.ProgressIndicatorDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.Dp
import design.adapt.cupertino.CupertinoSpinner
import design.adapt.cupertino.CupertinoSpinnerDefaults
import design.adapt.windows.WindowsProgressRing
import design.adapt.windows.WindowsProgressRingDefaults

@Composable
fun AdaptCircularIndicator(
modifier: AdaptModifier = AdaptModifier(),
color: Color = AdaptCircularIndicatorDefaults.Color,
configuration: AdaptCircularIndicatorConfiguration = AdaptCircularIndicatorDefaults.configuration(),
) {
when (LocalPlatform.current) {
// TODO: Separate Web from Android here
Platform.Android, Platform.Web -> configuration.android.progress?.let { progress ->
CircularProgressIndicator(
modifier = modifier.android,
color = color,
strokeWidth = configuration.android.strokeWidth,
trackColor = configuration.android.trackColor,
strokeCap = configuration.android.strokeCap,
progress = progress,
)
} ?: run {
CircularProgressIndicator(
modifier = modifier.android,
color = color,
strokeWidth = configuration.android.strokeWidth,
trackColor = configuration.android.trackColor,
strokeCap = configuration.android.strokeCap,
)
}

Platform.IOS -> CupertinoSpinner(
modifier = modifier.iOS,
color = color,
text = configuration.iOS.text,
)

Platform.MacOS -> CupertinoSpinner(
modifier = modifier.macOS,
color = color,
text = configuration.macOS.text,
)

Platform.Windows -> WindowsProgressRing(
modifier = modifier.windows,
progress = configuration.windows.progress,
color = color,
trackColor = configuration.windows.trackColor,
strokeWidth = configuration.windows.strokeWidth,
)
}
}

object AdaptCircularIndicatorDefaults {
@Composable
fun configuration(
android: AndroidCircularIndicatorConfiguration = AndroidCircularIndicatorConfiguration(
strokeWidth = ProgressIndicatorDefaults.CircularStrokeWidth,
trackColor = ProgressIndicatorDefaults.circularTrackColor,
strokeCap = ProgressIndicatorDefaults.CircularIndeterminateStrokeCap,
progress = null,
),
iOS: IOSCircularIndicatorConfiguration = IOSCircularIndicatorConfiguration(
text = null,
),
macOS: MacOSCircularIndicatorConfiguration = MacOSCircularIndicatorConfiguration(
text = null,
),
windows: WindowsCircularIndicatorConfiguration = WindowsCircularIndicatorConfiguration(
progress = null,
trackColor = androidx.compose.ui.graphics.Color.Transparent,
strokeWidth = WindowsProgressRingDefaults.StrokeWidthMedium,
),
) = AdaptCircularIndicatorConfiguration(
android = android,
iOS = iOS,
macOS = macOS,
windows = windows,
)

val Color @Composable get() = when(LocalPlatform.current) {
Platform.Android, Platform.Web -> ProgressIndicatorDefaults.circularColor
Platform.IOS -> CupertinoSpinnerDefaults.Color
Platform.MacOS -> CupertinoSpinnerDefaults.Color
Platform.Windows -> WindowsProgressRingDefaults.Color
}
}

@Immutable
class AdaptCircularIndicatorConfiguration(
val android: AndroidCircularIndicatorConfiguration,
val iOS: IOSCircularIndicatorConfiguration,
val macOS: MacOSCircularIndicatorConfiguration,
val windows: WindowsCircularIndicatorConfiguration,
)

@Immutable
class AndroidCircularIndicatorConfiguration(
val strokeWidth: Dp,
val trackColor: Color,
val strokeCap: StrokeCap,
val progress: (() -> Float)?,
)

@Immutable
class IOSCircularIndicatorConfiguration(
val text: (@Composable () -> Unit)?,
)

@Immutable
class MacOSCircularIndicatorConfiguration(
val text: (@Composable () -> Unit)?,
)

@Immutable
class WindowsCircularIndicatorConfiguration(
val progress: (() -> Float)?,
val trackColor: Color,
val strokeWidth: Dp,
)
60 changes: 45 additions & 15 deletions adapt/src/commonMain/kotlin/design/adapt/windows/WindowsButton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package design.adapt.windows

import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
Expand All @@ -26,6 +27,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
Expand All @@ -39,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import design.adapt.LocalContentColor

Expand Down Expand Up @@ -77,23 +80,23 @@ fun WindowsButton(
} else PaddingValues()
}

val containerColor = remember(colors, enabled, isHovered, isPressed) {
val containerColor by animateColorAsState(
when {
enabled && isHovered -> colors.hoveredContainerColor
enabled && isPressed -> colors.pressedContainerColor
enabled && isHovered -> colors.hoveredContainerColor
enabled -> colors.containerColor
else -> colors.disabledContainerColor
}
}
)

val contentColor = remember(colors, enabled, isHovered, isPressed) {
val contentColor by animateColorAsState(
when {
enabled && isHovered -> colors.hoveredContentColor
enabled && isPressed -> colors.pressedContentColor
enabled && isHovered -> colors.hoveredContentColor
enabled -> colors.contentColor
else -> colors.disabledContentColor
}
}
)

val processedIcon = remember(icon, isIconOnlyButton) {
icon?.let { safeIcon ->
Expand All @@ -110,8 +113,8 @@ fun WindowsButton(

val borderColors: List<Color>? = remember(colors, enabled, isPressed) {
when {
!enabled -> colors.disabledBorderColor?.let(::listOf)
enabled && isPressed -> colors.pressedBorderColor?.let(::listOf)
!enabled -> colors.disabledBorderColor?.let(::listOf)
else -> colors.borderColors
}
}
Expand All @@ -120,6 +123,10 @@ fun WindowsButton(
Row(
modifier = modifier
.padding(buttonMargin)
.defaultMinSize(
minWidth = WindowsButtonDefaults.defaultWidth(style = style, size = size),
minHeight = WindowsButtonDefaults.defaultHeight(style = style, size = size),
)
.focusBorder(interactionSource = interactionSource, shape = focusBorderShape)
.clickable(
enabled = enabled,
Expand All @@ -146,7 +153,10 @@ fun WindowsButton(
}
)
.padding(contentPadding),
horizontalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(
space = 8.dp,
alignment = Alignment.CenterHorizontally
),
verticalAlignment = Alignment.CenterVertically,
) {
if (iconSide == WindowsButtonIconSide.Start) processedIcon?.invoke()
Expand Down Expand Up @@ -278,24 +288,24 @@ object WindowsButtonDefaults {

return when {
!compact && accentStyle && textOnly -> PaddingValues(
start = 47.5.dp,
start = 12.dp,
top = 5.dp,
end = 47.5.dp,
end = 12.dp,
bottom = 7.dp
)

!compact && !accentStyle && textOnly -> PaddingValues(
start = 46.5.dp,
start = 11.dp,
top = 4.dp,
end = 46.5.dp,
end = 11.dp,
bottom = 6.dp
)

!compact && hasAllContent -> PaddingValues(
start = 34.5.dp,
start = 11.dp,
top = 4.dp,
end = 34.5.dp,
bottom = 6.dp,
end = 11.dp,
bottom = 4.dp,
)

!compact && iconOnly -> PaddingValues(7.dp)
Expand All @@ -309,6 +319,26 @@ object WindowsButtonDefaults {
else -> PaddingValues()
}
}

internal fun defaultWidth(style: WindowsButtonStyle, size: WindowsButtonSize): Dp {
val isCompact = size == WindowsButtonSize.Compact
return when {
!isCompact && (style == WindowsButtonStyle.Standard || style == WindowsButtonStyle.Subtle) -> 120.dp
!isCompact && style == WindowsButtonStyle.Accent -> 118.dp
isCompact -> 22.dp
else -> 0.dp
}
}

internal fun defaultHeight(style: WindowsButtonStyle, size: WindowsButtonSize): Dp {
val isCompact = size == WindowsButtonSize.Compact
return when {
!isCompact && (style == WindowsButtonStyle.Standard || style == WindowsButtonStyle.Subtle) -> 32.dp
!isCompact && style == WindowsButtonStyle.Accent -> 30.dp
isCompact -> 22.dp
else -> 0.dp
}
}
}

enum class WindowsButtonStyle { Standard, Accent, Subtle }
Expand Down
Loading

0 comments on commit 83c55be

Please sign in to comment.