Skip to content

Commit 096d489

Browse files
committed
#1846 animate new key map floating action instead of showing tap target
1 parent 1629fc2 commit 096d489

11 files changed

Lines changed: 188 additions & 151 deletions

File tree

base/src/main/java/io/github/sds100/keymapper/base/compose/ComposeColors.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ object ComposeColors {
88
val onPrimaryLight = Color(0xFFFFFFFF)
99
val primaryContainerLight = Color(0xFFD6E3FF)
1010
val onPrimaryContainerLight = Color(0xFF274777)
11+
val primaryContainerDarkerLight = Color(0xFFC4D6FF)
1112
val secondaryLight = Color(0xFF565F71)
1213
val onSecondaryLight = Color(0xFFFFFFFF)
1314
val secondaryContainerLight = Color(0xFFDAE2F9)
@@ -64,6 +65,7 @@ object ComposeColors {
6465
val onPrimaryDark = Color(0xFF0A305F)
6566
val primaryContainerDark = Color(0xFF274777)
6667
val onPrimaryContainerDark = Color(0xFFD6E3FF)
68+
val primaryContainerDarkerDark = Color(0xFF2A3C5A)
6769
val secondaryDark = Color(0xFFBEC6DC)
6870
val onSecondaryDark = Color(0xFF283141)
6971
val secondaryContainerDark = Color(0xFF3E4759)

base/src/main/java/io/github/sds100/keymapper/base/compose/ComposeCustomColors.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import io.github.sds100.keymapper.base.compose.ComposeColors.orangeContainerDark
2222
import io.github.sds100.keymapper.base.compose.ComposeColors.orangeContainerLight
2323
import io.github.sds100.keymapper.base.compose.ComposeColors.orangeDark
2424
import io.github.sds100.keymapper.base.compose.ComposeColors.orangeLight
25+
import io.github.sds100.keymapper.base.compose.ComposeColors.primaryContainerDarkerDark
26+
import io.github.sds100.keymapper.base.compose.ComposeColors.primaryContainerDarkerLight
2527

2628
/**
2729
* Stores the custom colors in a palette that changes
@@ -51,6 +53,7 @@ data class ComposeCustomColors(
5153
val onAmberContainer: Color = Color.Unspecified,
5254
val discord: Color = Color.Unspecified,
5355
val onDiscord: Color = Color.Unspecified,
56+
val primaryContainerDarker: Color = Color.Unspecified,
5457
) {
5558
companion object {
5659
val LightPalette = ComposeCustomColors(
@@ -74,6 +77,7 @@ data class ComposeCustomColors(
7477
onAmberContainer = onAmberContainerLight,
7578
discord = ComposeColors.discordLight,
7679
onDiscord = ComposeColors.onDiscordLight,
80+
primaryContainerDarker = primaryContainerDarkerLight,
7781
)
7882

7983
val DarkPalette = ComposeCustomColors(
@@ -97,6 +101,7 @@ data class ComposeCustomColors(
97101
onAmberContainer = onAmberContainerDark,
98102
discord = ComposeColors.discordDark,
99103
onDiscord = ComposeColors.onDiscordDark,
104+
primaryContainerDarker = primaryContainerDarkerDark,
100105
)
101106
}
102107

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.github.sds100.keymapper.base.home
2+
3+
import androidx.compose.animation.Animatable
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.material3.MaterialTheme
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.LaunchedEffect
8+
import androidx.compose.runtime.remember
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.tooling.preview.Preview
11+
import io.github.sds100.keymapper.base.compose.KeyMapperTheme
12+
import io.github.sds100.keymapper.base.compose.LocalCustomColorsPalette
13+
import io.github.sds100.keymapper.base.utils.ui.compose.CollapsableFloatingActionButton
14+
15+
@Composable
16+
fun AnimatedFloatingActionButton(
17+
modifier: Modifier = Modifier,
18+
pulse: Boolean,
19+
showFabText: Boolean,
20+
text: String,
21+
onClick: () -> Unit,
22+
) {
23+
val defaultColor = MaterialTheme.colorScheme.primaryContainer
24+
val pulseColor = LocalCustomColorsPalette.current.primaryContainerDarker
25+
26+
val animatedPulseColor = remember { Animatable(defaultColor) }
27+
28+
LaunchedEffect(pulse) {
29+
repeat(10) {
30+
animatedPulseColor.animateTo(pulseColor, tween(700))
31+
animatedPulseColor.animateTo(defaultColor, tween(700))
32+
}
33+
}
34+
35+
CollapsableFloatingActionButton(
36+
modifier = modifier,
37+
onClick = onClick,
38+
showText = showFabText,
39+
text = text,
40+
containerColor = animatedPulseColor.value,
41+
)
42+
}
43+
44+
@Preview
45+
@Composable
46+
private fun PreviewAnimatedFloatingActionButton() {
47+
KeyMapperTheme {
48+
AnimatedFloatingActionButton(
49+
pulse = false,
50+
showFabText = true,
51+
text = "New Key Map",
52+
onClick = {}
53+
)
54+
}
55+
}
56+
57+
@Preview
58+
@Composable
59+
private fun PreviewAnimatedFloatingActionButtonPulsing() {
60+
KeyMapperTheme {
61+
AnimatedFloatingActionButton(
62+
pulse = true,
63+
showFabText = true,
64+
text = "New Key Map",
65+
onClick = {}
66+
)
67+
}
68+
}

base/src/main/java/io/github/sds100/keymapper/base/home/HomeKeyMapListScreen.kt

Lines changed: 102 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,20 @@ import androidx.compose.ui.unit.Dp
5252
import androidx.compose.ui.unit.dp
5353
import androidx.core.net.toUri
5454
import androidx.lifecycle.compose.collectAsStateWithLifecycle
55-
import com.canopas.lib.showcase.IntroShowcase
5655
import io.github.sds100.keymapper.base.R
5756
import io.github.sds100.keymapper.base.actions.keyevent.FixKeyEventActionBottomSheet
5857
import io.github.sds100.keymapper.base.backup.ImportExportState
5958
import io.github.sds100.keymapper.base.backup.RestoreType
6059
import io.github.sds100.keymapper.base.compose.KeyMapperTheme
6160
import io.github.sds100.keymapper.base.constraints.ConstraintMode
6261
import io.github.sds100.keymapper.base.groups.GroupListItemModel
63-
import io.github.sds100.keymapper.base.onboarding.OnboardingTapTarget
6462
import io.github.sds100.keymapper.base.sorting.SortBottomSheet
6563
import io.github.sds100.keymapper.base.trigger.KeyMapListItemModel
6664
import io.github.sds100.keymapper.base.trigger.TriggerError
6765
import io.github.sds100.keymapper.base.utils.ShareUtils
6866
import io.github.sds100.keymapper.base.utils.ui.compose.CollapsableFloatingActionButton
6967
import io.github.sds100.keymapper.base.utils.ui.compose.ComposeChipModel
7068
import io.github.sds100.keymapper.base.utils.ui.compose.ComposeIconInfo
71-
import io.github.sds100.keymapper.base.utils.ui.compose.KeyMapperTapTarget
72-
import io.github.sds100.keymapper.base.utils.ui.compose.keyMapperShowcaseStyle
7369
import io.github.sds100.keymapper.base.utils.ui.compose.openUriSafe
7470
import io.github.sds100.keymapper.base.utils.ui.drawable
7571
import io.github.sds100.keymapper.common.utils.KMError
@@ -156,125 +152,113 @@ fun HomeKeyMapListScreen(
156152

157153
var keyMapListBottomPadding by remember { mutableStateOf(100.dp) }
158154

159-
IntroShowcase(
160-
showIntroShowCase = state.showCreateKeyMapTapTarget,
161-
onShowCaseCompleted = {
162-
viewModel.onTapTargetsCompleted()
155+
// Check if the list is empty to trigger pulsing animation
156+
val isListEmpty = state.listItems is State.Data &&
157+
(state.listItems as State.Data<List<KeyMapListItemModel>>).data.isEmpty()
158+
159+
HomeKeyMapListScreen(
160+
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
161+
snackbarState = snackbarState,
162+
floatingActionButton = {
163+
AnimatedVisibility(
164+
state.appBarState !is KeyMapAppBarState.Selecting,
165+
enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }),
166+
exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it }),
167+
) {
168+
AnimatedFloatingActionButton(
169+
modifier = Modifier
170+
.padding(bottom = fabBottomPadding)
171+
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.End)),
172+
pulse = isListEmpty,
173+
showFabText = viewModel.showFabText,
174+
text = stringResource(R.string.home_fab_new_key_map),
175+
onClick = viewModel::onNewKeyMapClick,
176+
)
177+
}
163178
},
164-
dismissOnClickOutside = true,
165-
) {
166-
HomeKeyMapListScreen(
167-
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
168-
snackbarState = snackbarState,
169-
floatingActionButton = {
170-
AnimatedVisibility(
171-
state.appBarState !is KeyMapAppBarState.Selecting,
172-
enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }),
173-
exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it }),
174-
) {
175-
CollapsableFloatingActionButton(
176-
modifier = Modifier
177-
.padding(bottom = fabBottomPadding)
178-
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.End))
179-
.introShowCaseTarget(
180-
index = 0,
181-
style = keyMapperShowcaseStyle(),
182-
) {
183-
KeyMapperTapTarget(
184-
OnboardingTapTarget.CREATE_KEY_MAP,
185-
onSkipClick = viewModel::onSkipTapTargetClick,
186-
)
187-
},
188-
onClick = viewModel::onNewKeyMapClick,
189-
showText = viewModel.showFabText,
190-
text = stringResource(R.string.home_fab_new_key_map),
179+
listContent = {
180+
KeyMapList(
181+
modifier = Modifier.animateContentSize(),
182+
lazyListState = rememberLazyListState(),
183+
listItems = state.listItems,
184+
footerText = stringResource(R.string.home_key_map_list_footer_text),
185+
isSelectable = state.appBarState is KeyMapAppBarState.Selecting,
186+
onClickKeyMap = viewModel::onKeyMapCardClick,
187+
onLongClickKeyMap = viewModel::onKeyMapCardLongClick,
188+
onSelectedChange = viewModel::onKeyMapSelectedChanged,
189+
onFixClick = viewModel::onFixClick,
190+
onTriggerErrorClick = viewModel::onFixTriggerError,
191+
bottomListPadding = keyMapListBottomPadding,
192+
)
193+
},
194+
appBarContent = {
195+
KeyMapListAppBar(
196+
state = state.appBarState,
197+
scrollBehavior = scrollBehavior,
198+
onSettingsClick = onSettingsClick,
199+
onAboutClick = onAboutClick,
200+
onSortClick = { viewModel.showSortBottomSheet = true },
201+
onHelpClick = { uriHandler.openUriSafe(ctx, helpUrl) },
202+
onExportClick = viewModel::onExportClick,
203+
onImportClick = { importFileLauncher.launch(FileUtils.MIME_TYPE_ALL) },
204+
onInputMethodPickerClick = viewModel::showInputMethodPicker,
205+
onTogglePausedClick = viewModel::onTogglePausedClick,
206+
onFixWarningClick = viewModel::onFixWarningClick,
207+
onBackClick = {
208+
if (!viewModel.onBackClick()) {
209+
finishActivity()
210+
}
211+
},
212+
onSelectAllClick = viewModel::onSelectAllClick,
213+
onNewGroupClick = viewModel::onNewGroupClick,
214+
onRenameGroupClick = viewModel::onRenameGroupClick,
215+
onEditGroupNameClick = viewModel::onEditGroupNameClick,
216+
onGroupClick = viewModel::onGroupClick,
217+
onDeleteGroupClick = viewModel::onDeleteGroupClick,
218+
onNewConstraintClick = viewModel::onNewGroupConstraintClick,
219+
onRemoveConstraintClick = viewModel::onRemoveGroupConstraintClick,
220+
onConstraintModeChanged = viewModel::onGroupConstraintModeChanged,
221+
onFixConstraintClick = viewModel::onFixClick,
222+
onKeyMapsEnabledChange = viewModel::onGroupKeyMapsEnabledChanged,
223+
)
224+
},
225+
selectionBottomSheet = {
226+
AnimatedVisibility(
227+
visible = state.appBarState is KeyMapAppBarState.Selecting,
228+
enter = slideInVertically { it },
229+
exit = slideOutVertically { it },
230+
) {
231+
val selectionState = (state.appBarState as? KeyMapAppBarState.Selecting)
232+
?: KeyMapAppBarState.Selecting(
233+
selectionCount = 0,
234+
selectedKeyMapsEnabled = SelectedKeyMapsEnabled.NONE,
235+
isAllSelected = false,
236+
groups = emptyList(),
237+
breadcrumbs = emptyList(),
238+
showThisGroup = false,
191239
)
192-
}
193-
},
194-
listContent = {
195-
KeyMapList(
196-
modifier = Modifier.animateContentSize(),
197-
lazyListState = rememberLazyListState(),
198-
listItems = state.listItems,
199-
footerText = stringResource(R.string.home_key_map_list_footer_text),
200-
isSelectable = state.appBarState is KeyMapAppBarState.Selecting,
201-
onClickKeyMap = viewModel::onKeyMapCardClick,
202-
onLongClickKeyMap = viewModel::onKeyMapCardLongClick,
203-
onSelectedChange = viewModel::onKeyMapSelectedChanged,
204-
onFixClick = viewModel::onFixClick,
205-
onTriggerErrorClick = viewModel::onFixTriggerError,
206-
bottomListPadding = keyMapListBottomPadding,
207-
)
208-
},
209-
appBarContent = {
210-
KeyMapListAppBar(
211-
state = state.appBarState,
212-
scrollBehavior = scrollBehavior,
213-
onSettingsClick = onSettingsClick,
214-
onAboutClick = onAboutClick,
215-
onSortClick = { viewModel.showSortBottomSheet = true },
216-
onHelpClick = { uriHandler.openUriSafe(ctx, helpUrl) },
217-
onExportClick = viewModel::onExportClick,
218-
onImportClick = { importFileLauncher.launch(FileUtils.MIME_TYPE_ALL) },
219-
onInputMethodPickerClick = viewModel::showInputMethodPicker,
220-
onTogglePausedClick = viewModel::onTogglePausedClick,
221-
onFixWarningClick = viewModel::onFixWarningClick,
222-
onBackClick = {
223-
if (!viewModel.onBackClick()) {
224-
finishActivity()
225-
}
240+
241+
SelectionBottomSheet(
242+
modifier = Modifier.onSizeChanged { size ->
243+
keyMapListBottomPadding =
244+
((size.height.dp / 2) - 100.dp).coerceAtLeast(0.dp)
226245
},
227-
onSelectAllClick = viewModel::onSelectAllClick,
246+
enabled = selectionState.selectionCount > 0,
247+
groups = selectionState.groups,
248+
breadcrumbs = selectionState.breadcrumbs,
249+
selectedKeyMapsEnabled = selectionState.selectedKeyMapsEnabled,
250+
onEnabledKeyMapsChange = viewModel::onEnabledKeyMapsChange,
251+
onDuplicateClick = viewModel::onDuplicateSelectedKeyMapsClick,
252+
onExportClick = viewModel::onExportSelectedKeyMaps,
253+
onDeleteClick = { showDeleteDialog = true },
254+
onGroupClick = viewModel::onSelectionGroupClick,
228255
onNewGroupClick = viewModel::onNewGroupClick,
229-
onRenameGroupClick = viewModel::onRenameGroupClick,
230-
onEditGroupNameClick = viewModel::onEditGroupNameClick,
231-
onGroupClick = viewModel::onGroupClick,
232-
onDeleteGroupClick = viewModel::onDeleteGroupClick,
233-
onNewConstraintClick = viewModel::onNewGroupConstraintClick,
234-
onRemoveConstraintClick = viewModel::onRemoveGroupConstraintClick,
235-
onConstraintModeChanged = viewModel::onGroupConstraintModeChanged,
236-
onFixConstraintClick = viewModel::onFixClick,
237-
onKeyMapsEnabledChange = viewModel::onGroupKeyMapsEnabledChanged,
256+
showThisGroup = selectionState.showThisGroup,
257+
onThisGroupClick = viewModel::onMoveToThisGroupClick,
238258
)
239-
},
240-
selectionBottomSheet = {
241-
AnimatedVisibility(
242-
visible = state.appBarState is KeyMapAppBarState.Selecting,
243-
enter = slideInVertically { it },
244-
exit = slideOutVertically { it },
245-
) {
246-
val selectionState = (state.appBarState as? KeyMapAppBarState.Selecting)
247-
?: KeyMapAppBarState.Selecting(
248-
selectionCount = 0,
249-
selectedKeyMapsEnabled = SelectedKeyMapsEnabled.NONE,
250-
isAllSelected = false,
251-
groups = emptyList(),
252-
breadcrumbs = emptyList(),
253-
showThisGroup = false,
254-
)
255-
256-
SelectionBottomSheet(
257-
modifier = Modifier.onSizeChanged { size ->
258-
keyMapListBottomPadding =
259-
((size.height.dp / 2) - 100.dp).coerceAtLeast(0.dp)
260-
},
261-
enabled = selectionState.selectionCount > 0,
262-
groups = selectionState.groups,
263-
breadcrumbs = selectionState.breadcrumbs,
264-
selectedKeyMapsEnabled = selectionState.selectedKeyMapsEnabled,
265-
onEnabledKeyMapsChange = viewModel::onEnabledKeyMapsChange,
266-
onDuplicateClick = viewModel::onDuplicateSelectedKeyMapsClick,
267-
onExportClick = viewModel::onExportSelectedKeyMaps,
268-
onDeleteClick = { showDeleteDialog = true },
269-
onGroupClick = viewModel::onSelectionGroupClick,
270-
onNewGroupClick = viewModel::onNewGroupClick,
271-
showThisGroup = selectionState.showThisGroup,
272-
onThisGroupClick = viewModel::onMoveToThisGroupClick,
273-
)
274-
}
275-
},
276-
)
277-
}
259+
}
260+
},
261+
)
278262
}
279263

280264
@Composable

base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListScreen.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ private fun EmptyKeyMapList(modifier: Modifier = Modifier) {
128128
val shrug = stringResource(R.string.shrug)
129129
val text = stringResource(R.string.home_key_map_list_empty)
130130
Text(
131-
modifier = Modifier.align(Alignment.Center),
131+
modifier = Modifier
132+
.align(Alignment.Center)
133+
.padding(horizontal = 48.dp),
132134
text = buildAnnotatedString {
133135
withStyle(MaterialTheme.typography.headlineLarge.toSpanStyle()) {
134136
append(shrug)

base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListState.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ import io.github.sds100.keymapper.common.utils.State
66
data class KeyMapListState(
77
val appBarState: KeyMapAppBarState,
88
val listItems: State<List<KeyMapListItemModel>>,
9-
val showCreateKeyMapTapTarget: Boolean = false,
109
)

0 commit comments

Comments
 (0)