Skip to content

Commit 6398837

Browse files
committed
First implementation of the Compose version
1 parent ca9c899 commit 6398837

File tree

105 files changed

+1513
-1480
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+1513
-1480
lines changed

art/project.dot.png

6.95 KB
Loading

build-conventions/src/main/groovy/speeddial.android-sample-conventions.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,20 @@ android {
3030
main.java.srcDirs += 'src/main/kotlin'
3131
test.java.srcDirs += 'src/test/kotlin'
3232
}
33+
3334
kotlinOptions {
3435
freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn'
3536
jvmTarget = config.android.javaVersion
3637
useIR = true
3738
}
39+
40+
buildFeatures {
41+
compose true
42+
}
43+
44+
composeOptions {
45+
kotlinCompilerExtensionVersion libs.versions.androidx.compose.get()
46+
}
3847
}
3948

4049
kapt {

config/detekt/detekt.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,9 @@ naming:
542542
EnumNaming:
543543
active: true
544544
enumEntryPattern: '[A-Z][_A-Z0-9]*' # Custom
545+
excludes: [
546+
'**/library-compose/src/main/kotlin/com/leinardi/android/speeddial/compose/**',
547+
] # Custom
545548
ForbiddenClassName:
546549
active: false
547550
forbiddenName: []
@@ -600,8 +603,8 @@ naming:
600603
TopLevelPropertyNaming:
601604
active: true
602605
excludes: [
603-
'**/modules/core-ui/src/main/kotlin/com/leinardi/speeddial/core/ui/component/**',
604-
] # Custom TODO
606+
'**/library-compose/src/main/kotlin/com/leinardi/android/speeddial/compose/**',
607+
] # Custom
605608
constantPattern: '[A-Z][_A-Z0-9]*'
606609
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
607610
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2022 Roberto Leinardi.
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+
* http://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.leinardi.android.speeddial.compose
18+
19+
@Suppress("EnumNaming")
20+
enum class ExpansionMode {
21+
Bottom, End, Start, Top
22+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2022 Roberto Leinardi.
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+
* http://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.leinardi.android.speeddial.compose
18+
19+
import androidx.compose.foundation.layout.Arrangement
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.Row
22+
import androidx.compose.foundation.layout.padding
23+
import androidx.compose.foundation.layout.size
24+
import androidx.compose.foundation.layout.widthIn
25+
import androidx.compose.foundation.shape.CornerSize
26+
import androidx.compose.material.Card
27+
import androidx.compose.material.ContentAlpha
28+
import androidx.compose.material.ExperimentalMaterialApi
29+
import androidx.compose.material.FloatingActionButton
30+
import androidx.compose.material.FloatingActionButtonDefaults
31+
import androidx.compose.material.FloatingActionButtonElevation
32+
import androidx.compose.material.LocalContentAlpha
33+
import androidx.compose.material.MaterialTheme
34+
import androidx.compose.material.ProvideTextStyle
35+
import androidx.compose.material.contentColorFor
36+
import androidx.compose.runtime.Composable
37+
import androidx.compose.runtime.CompositionLocalProvider
38+
import androidx.compose.ui.Alignment
39+
import androidx.compose.ui.Modifier
40+
import androidx.compose.ui.graphics.Color
41+
import androidx.compose.ui.graphics.Shape
42+
import androidx.compose.ui.unit.Dp
43+
import androidx.compose.ui.unit.dp
44+
45+
@ExperimentalMaterialApi
46+
@Composable
47+
fun FabWithLabel(
48+
onClick: () -> Unit,
49+
modifier: Modifier = Modifier,
50+
labelContent: @Composable (() -> Unit)? = null,
51+
labelBackgroundColor: Color = MaterialTheme.colors.surface,
52+
labelMaxWidth: Dp = 160.dp,
53+
labelContainerElevation: Dp = 2.dp,
54+
fabShape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
55+
fabElevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
56+
fabSize: Dp = 40.dp,
57+
fabBackgroundColor: Color = MaterialTheme.colors.primary,
58+
fabContentColor: Color = contentColorFor(fabBackgroundColor),
59+
fabContent: @Composable () -> Unit,
60+
) {
61+
Row(
62+
modifier = Modifier
63+
.padding(end = (56.dp - fabSize) / 2)
64+
.then(modifier),
65+
horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
66+
verticalAlignment = Alignment.CenterVertically,
67+
) {
68+
if (labelContent != null) {
69+
Card(
70+
modifier = Modifier.widthIn(max = labelMaxWidth),
71+
onClick = onClick,
72+
backgroundColor = labelBackgroundColor,
73+
elevation = labelContainerElevation,
74+
) {
75+
Box(
76+
modifier = Modifier
77+
.padding(horizontal = 8.dp, vertical = 4.dp),
78+
) {
79+
ProvideTextStyle(value = MaterialTheme.typography.subtitle2) {
80+
CompositionLocalProvider(
81+
LocalContentAlpha provides ContentAlpha.high,
82+
content = labelContent,
83+
)
84+
}
85+
}
86+
}
87+
}
88+
89+
FloatingActionButton(
90+
onClick = onClick,
91+
modifier = Modifier.size(fabSize),
92+
shape = fabShape,
93+
backgroundColor = fabBackgroundColor,
94+
contentColor = fabContentColor,
95+
elevation = fabElevation,
96+
) {
97+
fabContent()
98+
}
99+
}
100+
}

library-compose/src/main/kotlin/com/leinardi/android/speeddial/compose/SpeedDial.kt

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,121 @@
1616

1717
package com.leinardi.android.speeddial.compose
1818

19-
import androidx.compose.material.Text
19+
import androidx.compose.animation.AnimatedVisibility
20+
import androidx.compose.animation.ExperimentalAnimationApi
21+
import androidx.compose.animation.core.FiniteAnimationSpec
22+
import androidx.compose.animation.core.Transition
23+
import androidx.compose.animation.core.animateFloat
24+
import androidx.compose.animation.core.tween
25+
import androidx.compose.animation.core.updateTransition
26+
import androidx.compose.animation.fadeIn
27+
import androidx.compose.animation.fadeOut
28+
import androidx.compose.animation.slideInVertically
29+
import androidx.compose.animation.slideOutVertically
30+
import androidx.compose.foundation.layout.Arrangement
31+
import androidx.compose.foundation.layout.Box
32+
import androidx.compose.foundation.layout.Column
33+
import androidx.compose.foundation.layout.padding
34+
import androidx.compose.foundation.shape.CornerSize
35+
import androidx.compose.material.FloatingActionButton
36+
import androidx.compose.material.FloatingActionButtonDefaults
37+
import androidx.compose.material.FloatingActionButtonElevation
38+
import androidx.compose.material.Icon
39+
import androidx.compose.material.MaterialTheme
40+
import androidx.compose.material.contentColorFor
41+
import androidx.compose.material.icons.Icons
42+
import androidx.compose.material.icons.filled.Add
43+
import androidx.compose.material.icons.filled.Close
2044
import androidx.compose.runtime.Composable
45+
import androidx.compose.runtime.getValue
46+
import androidx.compose.ui.Alignment
2147
import androidx.compose.ui.Modifier
48+
import androidx.compose.ui.draw.rotate
49+
import androidx.compose.ui.graphics.Color
50+
import androidx.compose.ui.graphics.Shape
51+
import androidx.compose.ui.unit.IntOffset
52+
import androidx.compose.ui.unit.dp
2253

54+
@ExperimentalAnimationApi
2355
@Composable
2456
fun SpeedDial(
57+
state: SpeedDialState,
58+
onFabClick: (expanded: Boolean) -> Unit,
2559
modifier: Modifier = Modifier,
60+
// expansionMode: ExpansionMode = ExpansionMode.Top,
61+
fabShape: Shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)),
62+
fabElevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
63+
fabAnimationRotateAngle: Float = 45f,
64+
fabClosedContent: @Composable () -> Unit = { Icon(Icons.Default.Add, null) },
65+
fabClosedBackgroundColor: Color = MaterialTheme.colors.secondary,
66+
fabClosedContentColor: Color = contentColorFor(fabClosedBackgroundColor),
67+
fabOpenedContent: @Composable () -> Unit = { Icon(Icons.Default.Close, null) },
68+
fabOpenedBackgroundColor: Color = MaterialTheme.colors.secondary,
69+
fabOpenedContentColor: Color = contentColorFor(fabOpenedBackgroundColor),
70+
reverseAnimationOnClose: Boolean = false,
71+
contentAnimationDelayInMillis: Int = 20,
72+
content: SpeedDialScope.() -> Unit = {},
2673
) {
27-
Text("Hello world!")
74+
val scope = SpeedDialScope().apply(content)
75+
val itemScope = SpeedDialItemScope()
76+
val transition: Transition<SpeedDialState> = updateTransition(targetState = state, label = "SpeedDialStateTransition")
77+
val rotation: Float by transition.animateFloat(label = "SpeedDialStateRotation") { speedDialState ->
78+
if (speedDialState == SpeedDialState.Expanded) fabAnimationRotateAngle else 0f
79+
}
80+
81+
Column(
82+
modifier = modifier,
83+
verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.Bottom),
84+
horizontalAlignment = Alignment.End,
85+
) {
86+
// Action items
87+
repeat(scope.intervals.size) { index ->
88+
val animationInSpecIntOffset: FiniteAnimationSpec<IntOffset> =
89+
tween(delayMillis = contentAnimationDelayInMillis * (scope.intervals.size - index))
90+
val animationInSpecFloat: FiniteAnimationSpec<Float> = tween(delayMillis = contentAnimationDelayInMillis * (scope.intervals.size - index))
91+
val animationOutSpecIntOffset: FiniteAnimationSpec<IntOffset> = tween(delayMillis = contentAnimationDelayInMillis * index)
92+
val animationOutSpecFloat: FiniteAnimationSpec<Float> = tween(delayMillis = contentAnimationDelayInMillis * index)
93+
AnimatedVisibility(
94+
visible = state == SpeedDialState.Expanded,
95+
enter = fadeIn(animationInSpecFloat) + slideInVertically(animationInSpecIntOffset) { it / 2 },
96+
exit = if (reverseAnimationOnClose) {
97+
fadeOut(animationOutSpecFloat) + slideOutVertically(animationOutSpecIntOffset) { it / 2 }
98+
} else {
99+
fadeOut()
100+
},
101+
) {
102+
scope.intervals[index].item(itemScope)
103+
}
104+
}
105+
106+
// Main FAB
107+
val fabModifier = Modifier
108+
.padding(top = 8.dp)
109+
.rotate(rotation)
110+
111+
when (state) {
112+
SpeedDialState.Expanded -> FloatingActionButton(
113+
onClick = { onFabClick(true) },
114+
modifier = fabModifier,
115+
shape = fabShape,
116+
backgroundColor = fabOpenedBackgroundColor,
117+
contentColor = fabOpenedContentColor,
118+
elevation = fabElevation,
119+
content = {
120+
Box(modifier = Modifier.rotate(-fabAnimationRotateAngle)) {
121+
fabOpenedContent()
122+
}
123+
},
124+
)
125+
SpeedDialState.Collapsed -> FloatingActionButton(
126+
onClick = { onFabClick(false) },
127+
modifier = fabModifier,
128+
shape = fabShape,
129+
backgroundColor = fabClosedBackgroundColor,
130+
contentColor = fabClosedContentColor,
131+
elevation = fabElevation,
132+
content = fabClosedContent,
133+
)
134+
}
135+
}
28136
}

sample-compose/src/main/kotlin/com/leinardi/android/speeddial/sample/compose/MainActivity.kt renamed to library-compose/src/main/kotlin/com/leinardi/android/speeddial/compose/SpeedDialItemScope.kt

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.leinardi.android.speeddial.sample.compose
17+
package com.leinardi.android.speeddial.compose
1818

19-
import android.os.Bundle
20-
import androidx.activity.compose.setContent
21-
import androidx.appcompat.app.AppCompatActivity
22-
import com.leinardi.android.speeddial.compose.SpeedDial
19+
import androidx.compose.runtime.Stable
2320

2421
/**
25-
* Sample compose project
22+
* Receiver scope being used by the item content parameter of SpeedDial.
2623
*/
27-
class MainActivity : AppCompatActivity() { // AppCompatActivity is needed to be able to toggle Day/Night programmatically
28-
override fun onCreate(savedInstanceState: Bundle?) {
29-
super.onCreate(savedInstanceState)
30-
setContent {
31-
SpeedDial()
32-
}
33-
}
34-
}
24+
@Stable
25+
@SpeedDialScopeMarker
26+
class SpeedDialItemScope
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2022 Roberto Leinardi.
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+
* http://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.leinardi.android.speeddial.compose
18+
19+
import androidx.compose.animation.AnimatedVisibility
20+
import androidx.compose.animation.fadeIn
21+
import androidx.compose.animation.fadeOut
22+
import androidx.compose.foundation.background
23+
import androidx.compose.foundation.clickable
24+
import androidx.compose.foundation.layout.Box
25+
import androidx.compose.foundation.layout.fillMaxSize
26+
import androidx.compose.material.MaterialTheme
27+
import androidx.compose.runtime.Composable
28+
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.graphics.Color
30+
31+
@Composable
32+
fun SpeedDialOverlay(
33+
visible: Boolean,
34+
onClick: (() -> Unit),
35+
modifier: Modifier = Modifier,
36+
color: Color = MaterialTheme.colors.surface.copy(alpha = 0.66f),
37+
animate: Boolean = true,
38+
) {
39+
if (animate) {
40+
AnimatedVisibility(
41+
modifier = modifier,
42+
visible = visible,
43+
enter = fadeIn(),
44+
exit = fadeOut(),
45+
) {
46+
OverlayBox(color, onClick, modifier)
47+
}
48+
} else if (visible) {
49+
OverlayBox(color, onClick, modifier)
50+
}
51+
}
52+
53+
@Composable
54+
private fun OverlayBox(
55+
color: Color,
56+
onClick: (() -> Unit),
57+
modifier: Modifier = Modifier,
58+
) {
59+
Box(
60+
modifier = Modifier
61+
.fillMaxSize()
62+
.background(color)
63+
.clickable(onClick = onClick)
64+
.then(modifier),
65+
)
66+
}

0 commit comments

Comments
 (0)