Skip to content

Commit 5a8bad9

Browse files
committed
Adds long and double-click support to Button and Card
(Credit to @yschimke for much of the work)
1 parent 4e139ff commit 5a8bad9

File tree

5 files changed

+252
-12
lines changed

5 files changed

+252
-12
lines changed

compose-material/api/current.api

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ package com.google.android.horologist.compose.material {
77
}
88

99
public final class ButtonKt {
10-
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Button(androidx.compose.ui.graphics.vector.ImageVector imageVector, String contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ButtonColors colors, optional com.google.android.horologist.compose.material.ButtonSize buttonSize, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional boolean enabled);
11-
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Button(@DrawableRes int id, String contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ButtonColors colors, optional com.google.android.horologist.compose.material.ButtonSize buttonSize, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional boolean enabled);
10+
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Button(androidx.compose.ui.graphics.vector.ImageVector imageVector, String contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional androidx.wear.compose.material.ButtonColors colors, optional com.google.android.horologist.compose.material.ButtonSize buttonSize, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional boolean enabled);
11+
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Button(@DrawableRes int id, String contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onLongClick, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onDoubleClick, optional androidx.wear.compose.material.ButtonColors colors, optional com.google.android.horologist.compose.material.ButtonSize buttonSize, optional com.google.android.horologist.compose.material.IconRtlMode iconRtlMode, optional boolean enabled);
1212
}
1313

1414
@com.google.android.horologist.annotations.ExperimentalHorologistApi public abstract sealed class ButtonSize {
@@ -41,6 +41,10 @@ package com.google.android.horologist.compose.material {
4141
field public static final com.google.android.horologist.compose.material.ButtonSize.Small INSTANCE;
4242
}
4343

44+
public final class CardKt {
45+
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void Card(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> onDoubleClick, kotlin.jvm.functions.Function0<kotlin.Unit> onLongClick, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.graphics.painter.Painter backgroundPainter, optional long contentColor, optional boolean enabled, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.semantics.Role? role, kotlin.jvm.functions.Function0<kotlin.Unit> content);
46+
}
47+
4448
public final class ChipIconWithProgressKt {
4549
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void ChipIconWithProgress(optional androidx.compose.ui.Modifier modifier, optional com.google.android.horologist.images.base.paintable.Paintable? icon, optional boolean largeIcon, optional long progressIndicatorColor, optional long progressTrackColor);
4650
method @androidx.compose.runtime.Composable @com.google.android.horologist.annotations.ExperimentalHorologistApi public static void ChipIconWithProgress(float progress, optional androidx.compose.ui.Modifier modifier, optional com.google.android.horologist.images.base.paintable.Paintable? icon, optional boolean largeIcon, optional long progressIndicatorColor, optional long progressTrackColor);

compose-material/src/main/java/com/google/android/horologist/compose/material/Button.kt

+47-9
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,25 @@
1414
* limitations under the License.
1515
*/
1616

17+
@file:OptIn(ExperimentalFoundationApi::class)
18+
1719
package com.google.android.horologist.compose.material
1820

1921
import androidx.annotation.DrawableRes
22+
import androidx.compose.foundation.ExperimentalFoundationApi
23+
import androidx.compose.foundation.combinedClickable
24+
import androidx.compose.foundation.interaction.MutableInteractionSource
25+
import androidx.compose.foundation.layout.Box
26+
import androidx.compose.foundation.layout.fillMaxSize
2027
import androidx.compose.foundation.layout.size
28+
import androidx.compose.foundation.shape.CircleShape
2129
import androidx.compose.runtime.Composable
30+
import androidx.compose.runtime.remember
2231
import androidx.compose.ui.Alignment
2332
import androidx.compose.ui.Modifier
33+
import androidx.compose.ui.draw.clip
2434
import androidx.compose.ui.graphics.vector.ImageVector
35+
import androidx.compose.ui.semantics.Role
2536
import androidx.compose.ui.unit.Dp
2637
import androidx.wear.compose.material.Button
2738
import androidx.wear.compose.material.ButtonColors
@@ -49,6 +60,8 @@ public fun Button(
4960
contentDescription: String,
5061
onClick: () -> Unit,
5162
modifier: Modifier = Modifier,
63+
onLongClick: (() -> Unit)? = null,
64+
onDoubleClick: (() -> Unit)? = null,
5265
colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
5366
buttonSize: ButtonSize = ButtonSize.Default,
5467
iconRtlMode: IconRtlMode = IconRtlMode.Default,
@@ -58,6 +71,8 @@ public fun Button(
5871
icon = ImageVectorPaintable(imageVector),
5972
contentDescription = contentDescription,
6073
onClick = onClick,
74+
onLongClick = onLongClick,
75+
onDoubleClick = onDoubleClick,
6176
modifier = modifier,
6277
colors = colors,
6378
buttonSize = buttonSize,
@@ -78,6 +93,8 @@ public fun Button(
7893
contentDescription: String,
7994
onClick: () -> Unit,
8095
modifier: Modifier = Modifier,
96+
onLongClick: (() -> Unit)? = null,
97+
onDoubleClick: (() -> Unit)? = null,
8198
colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
8299
buttonSize: ButtonSize = ButtonSize.Default,
83100
iconRtlMode: IconRtlMode = IconRtlMode.Default,
@@ -87,6 +104,8 @@ public fun Button(
87104
icon = DrawableResPaintable(id),
88105
contentDescription = contentDescription,
89106
onClick = onClick,
107+
onLongClick = onLongClick,
108+
onDoubleClick = onDoubleClick,
90109
modifier = modifier,
91110
colors = colors,
92111
buttonSize = buttonSize,
@@ -102,27 +121,46 @@ internal fun Button(
102121
contentDescription: String,
103122
onClick: () -> Unit,
104123
modifier: Modifier = Modifier,
124+
onLongClick: (() -> Unit)? = null,
125+
onDoubleClick: (() -> Unit)? = null,
105126
colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
106127
buttonSize: ButtonSize = ButtonSize.Default,
107128
iconRtlMode: IconRtlMode = IconRtlMode.Default,
108129
enabled: Boolean = true,
109130
) {
131+
val interactionSource = remember { MutableInteractionSource() }
110132
Button(
111133
onClick = onClick,
112134
modifier = modifier.size(buttonSize.tapTargetSize),
113135
enabled = enabled,
114136
colors = colors,
137+
interactionSource = interactionSource,
115138
) {
116-
val iconModifier = Modifier
117-
.size(buttonSize.iconSize)
118-
.align(Alignment.Center)
139+
Box(
140+
modifier = Modifier
141+
.fillMaxSize()
142+
.clip(CircleShape)
143+
.combinedClickable(
144+
interactionSource = interactionSource,
145+
indication = null, // From material Button
146+
enabled = enabled,
147+
onClick = onClick,
148+
onLongClick = onLongClick,
149+
onDoubleClick = onDoubleClick,
150+
role = Role.Button,
151+
),
152+
) {
153+
val iconModifier = Modifier
154+
.size(buttonSize.iconSize)
155+
.align(Alignment.Center)
119156

120-
Icon(
121-
paintable = icon,
122-
contentDescription = contentDescription,
123-
modifier = iconModifier,
124-
rtlMode = iconRtlMode,
125-
)
157+
Icon(
158+
paintable = icon,
159+
contentDescription = contentDescription,
160+
modifier = iconModifier,
161+
rtlMode = iconRtlMode,
162+
)
163+
}
126164
}
127165
}
128166

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2023 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
@file:OptIn(ExperimentalFoundationApi::class)
18+
19+
package com.google.android.horologist.compose.material
20+
21+
import androidx.compose.foundation.ExperimentalFoundationApi
22+
import androidx.compose.foundation.combinedClickable
23+
import androidx.compose.foundation.interaction.MutableInteractionSource
24+
import androidx.compose.foundation.layout.Box
25+
import androidx.compose.foundation.layout.PaddingValues
26+
import androidx.compose.foundation.layout.fillMaxSize
27+
import androidx.compose.runtime.Composable
28+
import androidx.compose.runtime.remember
29+
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.graphics.Color
31+
import androidx.compose.ui.graphics.Shape
32+
import androidx.compose.ui.graphics.painter.Painter
33+
import androidx.compose.ui.semantics.Role
34+
import androidx.wear.compose.material.CardDefaults
35+
import androidx.wear.compose.material.MaterialTheme
36+
import com.google.android.horologist.annotations.ExperimentalHorologistApi
37+
38+
/**
39+
* This component is an alternative to [Card], adding support for long and double-clicks.
40+
*/
41+
@ExperimentalHorologistApi
42+
@Composable
43+
public fun Card(
44+
onClick: () -> Unit,
45+
onDoubleClick: () -> Unit,
46+
onLongClick: () -> Unit,
47+
modifier: Modifier = Modifier,
48+
backgroundPainter: Painter = CardDefaults.cardBackgroundPainter(),
49+
contentColor: Color = MaterialTheme.colors.onSurfaceVariant,
50+
enabled: Boolean = true,
51+
contentPadding: PaddingValues = CardDefaults.ContentPadding,
52+
shape: Shape = MaterialTheme.shapes.large,
53+
role: Role? = null,
54+
content: @Composable () -> Unit,
55+
) {
56+
val interactionSource = remember { MutableInteractionSource() }
57+
androidx.wear.compose.material.Card(
58+
onClick = onClick,
59+
modifier = modifier,
60+
backgroundPainter = backgroundPainter,
61+
contentColor = contentColor,
62+
enabled = enabled,
63+
contentPadding = contentPadding,
64+
shape = shape,
65+
interactionSource = interactionSource,
66+
role = role,
67+
) {
68+
Box(
69+
modifier = Modifier
70+
.fillMaxSize()
71+
.combinedClickable(
72+
interactionSource = interactionSource,
73+
indication = null,
74+
enabled = enabled,
75+
onClick = onClick,
76+
onLongClick = onLongClick,
77+
onDoubleClick = onDoubleClick,
78+
role = Role.Button,
79+
),
80+
) {
81+
content()
82+
}
83+
}
84+
}

sample/src/main/java/com/google/android/horologist/materialcomponents/SampleButtonScreen.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ internal fun SampleButtonScreen(
3939
Button(
4040
imageVector = Icons.Default.Check,
4141
contentDescription = "contentDescription",
42-
onClick = { },
42+
onClick = { println("Click") },
43+
onLongClick = { println("LongClick") },
44+
onDoubleClick = { println("DoubleClick") },
4345
colors = ButtonDefaults.iconButtonColors(),
4446
)
4547
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2023 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.horologist.materialcomponents
18+
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.PaddingValues
21+
import androidx.compose.runtime.Composable
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.graphics.Color
24+
import androidx.compose.ui.res.painterResource
25+
import androidx.compose.ui.tooling.preview.Preview
26+
import androidx.compose.ui.unit.dp
27+
import androidx.wear.compose.material.MaterialTheme
28+
import androidx.wear.compose.material.Text
29+
import com.google.android.horologist.compose.layout.ScalingLazyColumn
30+
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
31+
import com.google.android.horologist.compose.layout.rememberColumnState
32+
import com.google.android.horologist.compose.material.Card
33+
34+
@Preview
35+
@Composable
36+
fun SampleCardScreenPreview() {
37+
val state = rememberColumnState()
38+
SampleCardScreen(columnState = state)
39+
}
40+
41+
@Composable
42+
internal fun SampleCardScreen(
43+
modifier: Modifier = Modifier,
44+
columnState: ScalingLazyColumnState,
45+
) {
46+
ScalingLazyColumn(
47+
modifier = modifier,
48+
columnState = columnState,
49+
) {
50+
item {
51+
Card(
52+
onClick = { println("Click") },
53+
onLongClick = { println("LongClick") },
54+
onDoubleClick = { println("DoubleClick") },
55+
modifier = Modifier.background(Color.Cyan),
56+
) {
57+
Text("Hello, Card")
58+
}
59+
}
60+
item {
61+
Card(
62+
onClick = { println("Click") },
63+
onLongClick = { println("LongClick") },
64+
onDoubleClick = { println("DoubleClick") },
65+
modifier = Modifier.background(Color.Cyan),
66+
shape = MaterialTheme.shapes.medium,
67+
) {
68+
Text("Hello, Card")
69+
}
70+
}
71+
item {
72+
Card(
73+
onClick = { println("Click") },
74+
onLongClick = { println("LongClick") },
75+
onDoubleClick = { println("DoubleClick") },
76+
backgroundPainter = painterResource(id = android.R.drawable.ic_dialog_alert),
77+
) {
78+
Text("Hello, Card")
79+
}
80+
}
81+
item {
82+
Card(
83+
onClick = { println("Click") },
84+
onLongClick = { println("LongClick") },
85+
onDoubleClick = { println("DoubleClick") },
86+
contentPadding = PaddingValues(24.dp),
87+
) {
88+
Text("Hello, Card")
89+
}
90+
}
91+
item {
92+
Card(
93+
onClick = { println("Click") },
94+
onLongClick = { println("LongClick") },
95+
onDoubleClick = { println("DoubleClick") },
96+
contentColor = MaterialTheme.colors.primaryVariant,
97+
) {
98+
Text("Hello, Card")
99+
}
100+
}
101+
item {
102+
Card(
103+
onClick = { println("Click") },
104+
onLongClick = { println("LongClick") },
105+
onDoubleClick = { println("DoubleClick") },
106+
enabled = false,
107+
) {
108+
Text("Hello, Card")
109+
}
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)