Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add haptic feedback #1457

Open
wants to merge 6 commits into
base: compose-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
BenjaminHalko marked this conversation as resolved.
Show resolved Hide resolved
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -67,14 +68,14 @@ fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) {
headline = R.string.auto_updates_dialog_manager,
icon = Icons.Outlined.Update,
checked = managerEnabled,
onCheckedChange = { managerEnabled = it }
onCheckedChange = hapticCheckbox { managerEnabled = it }
)
Divider()
AutoUpdatesItem(
headline = R.string.auto_updates_dialog_patches,
icon = Icons.Outlined.Source,
checked = patchesEnabled,
onCheckedChange = { patchesEnabled = it }
onCheckedChange = hapticCheckbox { patchesEnabled = it }
)

Text(
Expand Down
42 changes: 42 additions & 0 deletions app/src/main/java/app/revanced/manager/ui/component/Haptics.kt
@@ -0,0 +1,42 @@
package app.revanced.manager.ui.component

import android.os.Build
import android.view.HapticFeedbackConstants
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalView



@Composable
fun hapticSwitch(onClick: (Boolean) -> Unit): (Boolean) -> Unit {
val view = LocalView.current
return {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
view.performHapticFeedback(if (it) HapticFeedbackConstants.TOGGLE_ON else HapticFeedbackConstants.TOGGLE_OFF)
} else {
view.performHapticFeedback(if (it) HapticFeedbackConstants.VIRTUAL_KEY else HapticFeedbackConstants.CLOCK_TICK)
}
onClick(it)
}
}

@Composable
fun hapticSwitch(currentValue: Boolean, onClick: () -> Unit): () -> Unit {
val func = hapticSwitch { _ -> onClick() }
return { func(!currentValue) }
}

@Composable
fun hapticCheckbox(onClick: (Boolean) -> Unit): (Boolean) -> Unit {
val view = LocalView.current
return {
view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK)
onClick(it)
}
}

@Composable
fun hapticCheckboxToggle(onClick: () -> Unit): () -> Unit {
val func = hapticCheckbox { _ -> onClick() }
return { func(false) }
}
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.ui.component.TextInputDialog
import app.revanced.manager.ui.component.hapticSwitch
import app.revanced.manager.util.isDebuggable

@Composable
Expand Down Expand Up @@ -115,17 +116,18 @@ fun BaseBundleDialog(
extraFields()

if (remoteUrl != null) {
val autoUpdateClickable = hapticSwitch(autoUpdate) { onAutoUpdateChange(!autoUpdate) }
BundleListItem(
headlineText = stringResource(R.string.automatically_update),
supportingText = stringResource(R.string.automatically_update_description),
trailingContent = {
Switch(
checked = autoUpdate,
onCheckedChange = onAutoUpdateChange
onCheckedChange = hapticSwitch { onAutoUpdateChange(it) }
)
},
modifier = Modifier.clickable {
onAutoUpdateChange(!autoUpdate)
autoUpdateClickable()
}
)
}
Expand Down
Expand Up @@ -21,13 +21,15 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.propsOrNullFlow
import app.revanced.manager.ui.component.hapticCheckbox
import kotlinx.coroutines.flow.map

@OptIn(ExperimentalFoundationApi::class)
Expand Down Expand Up @@ -65,16 +67,14 @@ fun BundleItem(
.height(64.dp)
.fillMaxWidth()
.combinedClickable(
onClick = {
viewBundleDialogPage = true
},
onLongClick = onSelect,
onClick = { viewBundleDialogPage = true },
onLongClick = { onSelect() },
),
leadingContent = {
if(selectable) {
Checkbox(
checked = isBundleSelected,
onCheckedChange = toggleSelection,
onCheckedChange = hapticCheckbox { toggleSelection(it) },
)
}
},
Expand Down
Expand Up @@ -24,10 +24,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
BenjaminHalko marked this conversation as resolved.
Show resolved Hide resolved
import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.patcher.patch.Option
import app.revanced.manager.ui.component.hapticSwitch
import app.revanced.manager.util.toast
import org.koin.compose.rememberKoinInject

Expand Down Expand Up @@ -150,9 +152,9 @@ private val optionImplementations = mapOf<String, OptionImpl>(

OptionListItem(
option = option,
onClick = { setValue(!current) }
onClick = hapticSwitch(current) { setValue(!current) }
) {
Switch(checked = current, onCheckedChange = setValue)
Switch(checked = current, onCheckedChange = hapticSwitch { setValue(it) })
}
},
"String" to { option, value, setValue ->
Expand All @@ -179,7 +181,7 @@ private val optionImplementations = mapOf<String, OptionImpl>(

OptionListItem(
option = option,
onClick = ::showInputDialog
onClick = ::showInputDialog,
) {
IconButton(onClick = ::showInputDialog) {
Icon(
Expand Down
Expand Up @@ -7,8 +7,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
BenjaminHalko marked this conversation as resolved.
Show resolved Hide resolved
import androidx.compose.ui.res.stringResource
import app.revanced.manager.domain.manager.base.Preference
import app.revanced.manager.ui.component.hapticSwitch
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

Expand All @@ -23,7 +25,7 @@ fun BooleanItem(

BooleanItem(
value = value,
onValueChange = { coroutineScope.launch { preference.update(it) } },
onValueChange = hapticSwitch { coroutineScope.launch { preference.update(it) } },
headline = headline,
description = description
)
Expand Down
@@ -1,5 +1,6 @@
package app.revanced.manager.ui.screen

import android.view.HapticFeedbackConstants
BenjaminHalko marked this conversation as resolved.
Show resolved Hide resolved
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -51,6 +52,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.LocalView
BenjaminHalko marked this conversation as resolved.
Show resolved Hide resolved
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
Expand All @@ -62,6 +64,8 @@ import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.patcher.patch.PatchInfo
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.Countdown
import app.revanced.manager.ui.component.hapticCheckbox
import app.revanced.manager.ui.component.hapticCheckboxToggle
import app.revanced.manager.ui.component.patches.OptionItem
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_SUPPORTED
Expand Down Expand Up @@ -386,6 +390,7 @@ fun SelectionWarningDialog(
var dismissPermanently by rememberSaveable {
mutableStateOf(false)
}
val disableCheckbox = hapticCheckbox { dismissPermanently = !dismissPermanently }

AlertDialog(
onDismissRequest = onCancel,
Expand Down Expand Up @@ -438,15 +443,11 @@ fun SelectionWarningDialog(
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(0.dp),
modifier = Modifier.clickable {
dismissPermanently = !dismissPermanently
}
modifier = Modifier.clickable { disableCheckbox(false) }
) {
Checkbox(
checked = dismissPermanently,
onCheckedChange = {
dismissPermanently = it
}
onCheckedChange = hapticCheckbox { dismissPermanently = it }
)
Text(stringResource(R.string.permanent_dismiss))
}
Expand All @@ -462,28 +463,31 @@ fun PatchItem(
selected: Boolean,
onToggle: () -> Unit,
supported: Boolean = true
) = ListItem(
modifier = Modifier
.let { if (!supported) it.alpha(0.5f) else it }
.clickable(enabled = supported, onClick = onToggle)
.fillMaxSize(),
leadingContent = {
Checkbox(
checked = selected,
onCheckedChange = { onToggle() },
enabled = supported
)
},
headlineContent = { Text(patch.name) },
supportingContent = patch.description?.let { { Text(it) } },
trailingContent = {
if (patch.options?.isNotEmpty() == true) {
IconButton(onClick = onOptionsDialog, enabled = supported) {
Icon(Icons.Outlined.Settings, null)
) {
val haptic = hapticCheckboxToggle { onToggle() }
return ListItem(
modifier = Modifier
.let { if (!supported) it.alpha(0.5f) else it }
.clickable(enabled = supported, onClick = haptic)
.fillMaxSize(),
leadingContent = {
Checkbox(
checked = selected,
onCheckedChange = { haptic() },
enabled = supported
)
},
headlineContent = { Text(patch.name) },
supportingContent = patch.description?.let { { Text(it) } },
trailingContent = {
if (patch.options?.isNotEmpty() == true) {
IconButton(onClick = onOptionsDialog, enabled = supported) {
Icon(Icons.Outlined.Settings, null)
}
}
}
}
)
)
}

@Composable
fun ListHeader(
Expand Down