feat: Add haptic feedback (#1457)

Co-authored-by: Ushie <ushiekane@gmail.com>
This commit is contained in:
Benjamin 2024-11-06 12:48:40 -08:00 committed by GitHub
parent 697386c36c
commit b4c37e6ddc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 326 additions and 114 deletions

View File

@ -8,7 +8,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Source import androidx.compose.material.icons.outlined.Source
import androidx.compose.material.icons.outlined.Update import androidx.compose.material.icons.outlined.Update
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
@ -24,6 +23,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.haptics.HapticCheckbox
@Composable @Composable
fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) { fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) {
@ -76,6 +76,6 @@ private fun AutoUpdatesItem(
) = ListItem( ) = ListItem(
leadingContent = { Icon(icon, null) }, leadingContent = { Icon(icon, null) },
headlineContent = { Text(stringResource(headline)) }, headlineContent = { Text(stringResource(headline)) },
trailingContent = { Checkbox(checked = checked, onCheckedChange = null) }, trailingContent = { HapticCheckbox(checked = checked, onCheckedChange = null) },
modifier = Modifier.clickable { onCheckedChange(!checked) } modifier = Modifier.clickable { onCheckedChange(!checked) }
) )

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.haptics.HapticCheckbox
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -70,7 +71,7 @@ fun AvailableUpdateDialog(
}, },
leadingContent = { leadingContent = {
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
Checkbox(checked = dontShowAgain, onCheckedChange = { dontShowAgain = it }) HapticCheckbox(checked = dontShowAgain, onCheckedChange = { dontShowAgain = it })
} }
} }
) )

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.TextInputDialog import app.revanced.manager.ui.component.TextInputDialog
import app.revanced.manager.ui.component.haptics.HapticSwitch
@Composable @Composable
fun BaseBundleDialog( fun BaseBundleDialog(
@ -89,7 +90,7 @@ fun BaseBundleDialog(
headlineText = stringResource(R.string.bundle_auto_update), headlineText = stringResource(R.string.bundle_auto_update),
supportingText = stringResource(R.string.bundle_auto_update_description), supportingText = stringResource(R.string.bundle_auto_update_description),
trailingContent = { trailingContent = {
Switch( HapticSwitch(
checked = autoUpdate, checked = autoUpdate,
onCheckedChange = onAutoUpdateChange onCheckedChange = onAutoUpdateChange
) )

View File

@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.material.icons.outlined.ErrorOutline
import androidx.compose.material.icons.outlined.Warning import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -27,6 +26,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.domain.bundles.PatchBundleSource import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -71,7 +71,7 @@ fun BundleItem(
), ),
leadingContent = if (selectable) { leadingContent = if (selectable) {
{ {
Checkbox( HapticCheckbox(
checked = isBundleSelected, checked = isBundleSelected,
onCheckedChange = toggleSelection, onCheckedChange = toggleSelection,
) )

View File

@ -10,26 +10,9 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Topic import androidx.compose.material.icons.filled.Topic
import androidx.compose.material3.Checkbox import androidx.compose.material3.*
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.*
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
@ -37,6 +20,8 @@ import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.ui.component.AlertDialogExtended import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.TextHorizontalPadding import app.revanced.manager.ui.component.TextHorizontalPadding
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.ui.model.BundleType import app.revanced.manager.ui.model.BundleType
import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.APK_MIMETYPE
import app.revanced.manager.util.JAR_MIMETYPE import app.revanced.manager.util.JAR_MIMETYPE
@ -170,7 +155,7 @@ fun SelectBundleTypeStep(
overlineContent = { Text(stringResource(R.string.recommended)) }, overlineContent = { Text(stringResource(R.string.recommended)) },
supportingContent = { Text(stringResource(R.string.remote_bundle_description)) }, supportingContent = { Text(stringResource(R.string.remote_bundle_description)) },
leadingContent = { leadingContent = {
RadioButton( HapticRadioButton(
selected = bundleType == BundleType.Remote, selected = bundleType == BundleType.Remote,
onClick = null onClick = null
) )
@ -186,7 +171,7 @@ fun SelectBundleTypeStep(
supportingContent = { Text(stringResource(R.string.local_bundle_description)) }, supportingContent = { Text(stringResource(R.string.local_bundle_description)) },
overlineContent = { }, overlineContent = { },
leadingContent = { leadingContent = {
RadioButton( HapticRadioButton(
selected = bundleType == BundleType.Local, selected = bundleType == BundleType.Local,
onClick = null onClick = null
) )
@ -263,7 +248,7 @@ fun ImportBundleStep(
headlineContent = { Text(stringResource(R.string.auto_update)) }, headlineContent = { Text(stringResource(R.string.auto_update)) },
leadingContent = { leadingContent = {
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
Checkbox( HapticCheckbox(
checked = autoUpdate, checked = autoUpdate,
onCheckedChange = { onCheckedChange = {
onAutoUpdateChange(!autoUpdate) onAutoUpdateChange(!autoUpdate)

View File

@ -0,0 +1,40 @@
package app.revanced.manager.ui.component.haptics
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxColors
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
@Composable
fun HapticCheckbox (
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: CheckboxColors = CheckboxDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
val checkedState = remember { mutableStateOf(checked) }
// Perform haptic feedback
if (checkedState.value != checked) {
val view = LocalView.current
view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK)
checkedState.value = checked
}
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
enabled = enabled,
colors = colors,
interactionSource = interactionSource
)
}

View File

@ -0,0 +1,48 @@
package app.revanced.manager.ui.component.haptics
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.FloatingActionButtonElevation
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalView
@Composable
fun HapticExtendedFloatingActionButton (
text: @Composable () -> Unit,
icon: @Composable () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
expanded: Boolean = true,
shape: Shape = FloatingActionButtonDefaults.extendedFabShape,
containerColor: Color = FloatingActionButtonDefaults.containerColor,
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val view = LocalView.current
ExtendedFloatingActionButton(
text = text,
icon = icon,
onClick = {
// Perform haptic feedback
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
onClick()
},
modifier = modifier,
expanded = expanded,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
interactionSource = interactionSource
)
}

View File

@ -0,0 +1,44 @@
package app.revanced.manager.ui.component.haptics
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.FloatingActionButtonElevation
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalView
@Composable
fun HapticFloatingActionButton (
onClick: () -> Unit,
modifier: Modifier = Modifier,
shape: Shape = FloatingActionButtonDefaults.shape,
containerColor: Color = FloatingActionButtonDefaults.containerColor,
contentColor: Color = contentColorFor(containerColor),
elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit,
) {
val view = LocalView.current
FloatingActionButton(
onClick = {
// Perform haptic feedback
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
onClick()
},
modifier = modifier,
shape = shape,
containerColor = containerColor,
contentColor = contentColor,
elevation = elevation,
interactionSource = interactionSource,
content = content
)
}

View File

@ -0,0 +1,42 @@
package app.revanced.manager.ui.component.haptics
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.RadioButton
import androidx.compose.material3.RadioButtonColors
import androidx.compose.material3.RadioButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
@Composable
fun HapticRadioButton (
selected: Boolean,
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: RadioButtonColors = RadioButtonDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
val selectedState = remember { mutableStateOf(selected) }
// Perform haptic feedback
if (selectedState.value != selected) {
if (selected) {
val view = LocalView.current
view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK)
}
selectedState.value = selected
}
RadioButton(
selected = selected,
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = colors,
interactionSource = interactionSource
)
}

View File

@ -0,0 +1,46 @@
package app.revanced.manager.ui.component.haptics
import android.os.Build
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchColors
import androidx.compose.material3.SwitchDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
@Composable
fun HapticSwitch(
checked: Boolean,
onCheckedChange: ((Boolean) -> Unit),
modifier: Modifier = Modifier,
thumbContent: (@Composable () -> Unit)? = null,
enabled: Boolean = true,
colors: SwitchColors = SwitchDefaults.colors(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val checkedState = remember { mutableStateOf(checked) }
// Perform haptic feedback
if (checkedState.value != checked) {
val view = LocalView.current
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
view.performHapticFeedback(if (checked) HapticFeedbackConstants.TOGGLE_ON else HapticFeedbackConstants.TOGGLE_OFF)
} else {
view.performHapticFeedback(if (checked) HapticFeedbackConstants.VIRTUAL_KEY else HapticFeedbackConstants.CLOCK_TICK)
}
checkedState.value = checked
}
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
modifier = modifier,
thumbContent = thumbContent,
enabled = enabled,
colors = colors,
interactionSource = interactionSource,
)
}

View File

@ -0,0 +1,43 @@
package app.revanced.manager.ui.component.haptics
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Tab
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
@Composable
fun HapticTab (
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
text: @Composable (() -> Unit)? = null,
icon: @Composable (() -> Unit)? = null,
selectedContentColor: Color = LocalContentColor.current,
unselectedContentColor: Color = selectedContentColor,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
) {
val view = LocalView.current
Tab(
selected = selected,
onClick = {
// Perform haptic feedback
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
onClick()
},
modifier = modifier,
enabled = enabled,
text = text,
icon = icon,
selectedContentColor = selectedContentColor,
unselectedContentColor = unselectedContentColor,
interactionSource = interactionSource
)
}

View File

@ -2,12 +2,7 @@ package app.revanced.manager.ui.component.patcher
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.AlertDialog import androidx.compose.material3.*
import androidx.compose.material3.Button
import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -17,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.ui.component.haptics.HapticRadioButton
@Composable @Composable
fun InstallPickerDialog( fun InstallPickerDialog(
@ -49,7 +45,7 @@ fun InstallPickerDialog(
ListItem( ListItem(
modifier = Modifier.clickable { selectedInstallType = it }, modifier = Modifier.clickable { selectedInstallType = it },
leadingContent = { leadingContent = {
RadioButton( HapticRadioButton(
selected = selectedInstallType == it, selected = selectedInstallType == it,
onClick = null onClick = null
) )

View File

@ -20,53 +20,31 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.DragHandle import androidx.compose.material.icons.filled.DragHandle
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.*
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.*
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Folder
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog as ComposeDialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.patcher.patch.Option import app.revanced.manager.patcher.patch.Option
import app.revanced.manager.ui.component.AlertDialogExtended import app.revanced.manager.ui.component.*
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.FloatInputDialog import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.ui.component.IntInputDialog import app.revanced.manager.ui.component.haptics.HapticSwitch
import app.revanced.manager.ui.component.LongInputDialog
import app.revanced.manager.util.isScrollingUp import app.revanced.manager.util.isScrollingUp
import app.revanced.manager.util.mutableStateSetOf import app.revanced.manager.util.mutableStateSetOf
import app.revanced.manager.util.saver.snapshotStateListSaver import app.revanced.manager.util.saver.snapshotStateListSaver
@ -80,6 +58,7 @@ import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyColumnState import sh.calvin.reorderable.rememberReorderableLazyColumnState
import java.io.Serializable import java.io.Serializable
import kotlin.random.Random import kotlin.random.Random
import androidx.compose.ui.window.Dialog as ComposeDialog
private class OptionEditorScope<T : Any>( private class OptionEditorScope<T : Any>(
private val editor: OptionEditor<T>, private val editor: OptionEditor<T>,
@ -335,7 +314,7 @@ private object BooleanOptionEditor : OptionEditor<Boolean> {
@Composable @Composable
override fun ListItemTrailingContent(scope: OptionEditorScope<Boolean>) { override fun ListItemTrailingContent(scope: OptionEditorScope<Boolean>) {
Switch(checked = scope.current, onCheckedChange = scope.setValue) HapticSwitch(checked = scope.current, onCheckedChange = scope.setValue)
} }
@Composable @Composable
@ -422,7 +401,7 @@ private class PresetOptionEditor<T : Any>(private val innerEditor: OptionEditor<
headlineContent = { Text(title) }, headlineContent = { Text(title) },
supportingContent = value?.toString()?.let { { Text(it) } }, supportingContent = value?.toString()?.let { { Text(it) } },
leadingContent = { leadingContent = {
RadioButton( HapticRadioButton(
selected = selectedPreset == presetKey, selected = selectedPreset == presetKey,
onClick = { selectedPreset = presetKey } onClick = { selectedPreset = presetKey }
) )
@ -568,7 +547,7 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
floatingActionButton = { floatingActionButton = {
if (deleteMode) return@Scaffold if (deleteMode) return@Scaffold
ExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.add)) }, text = { Text(stringResource(R.string.add)) },
icon = { icon = {
Icon( Icon(

View File

@ -2,13 +2,13 @@ package app.revanced.manager.ui.component.settings
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material3.Switch
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.revanced.manager.domain.manager.base.Preference import app.revanced.manager.domain.manager.base.Preference
import app.revanced.manager.ui.component.haptics.HapticSwitch
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,7 +45,7 @@ fun BooleanItem(
headlineContent = stringResource(headline), headlineContent = stringResource(headline),
supportingContent = stringResource(description), supportingContent = stringResource(description),
trailingContent = { trailingContent = {
Switch( HapticSwitch(
checked = value, checked = value,
onCheckedChange = onValueChange, onCheckedChange = onValueChange,
) )

View File

@ -33,6 +33,8 @@ import app.revanced.manager.ui.component.AvailableUpdateDialog
import app.revanced.manager.ui.component.NotificationCard import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.bundle.BundleItem import app.revanced.manager.ui.component.bundle.BundleItem
import app.revanced.manager.ui.component.bundle.BundleTopBar import app.revanced.manager.ui.component.bundle.BundleTopBar
import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticTab
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
import app.revanced.manager.ui.viewmodel.DashboardViewModel import app.revanced.manager.ui.viewmodel.DashboardViewModel
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
@ -168,7 +170,7 @@ fun DashboardScreen(
} }
}, },
floatingActionButton = { floatingActionButton = {
FloatingActionButton( HapticFloatingActionButton(
onClick = { onClick = {
vm.cancelSourceSelection() vm.cancelSourceSelection()
@ -181,7 +183,7 @@ fun DashboardScreen(
DashboardPage.BUNDLES.ordinal DashboardPage.BUNDLES.ordinal
) )
} }
return@FloatingActionButton return@HapticFloatingActionButton
} }
onAppSelectorClick() onAppSelectorClick()
@ -201,7 +203,7 @@ fun DashboardScreen(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
) { ) {
DashboardPage.entries.forEachIndexed { index, page -> DashboardPage.entries.forEachIndexed { index, page ->
Tab( HapticTab(
selected = pagerState.currentPage == index, selected = pagerState.currentPage == index,
onClick = { composableScope.launch { pagerState.animateScrollToPage(index) } }, onClick = { composableScope.launch { pagerState.animateScrollToPage(index) } },
text = { Text(stringResource(page.titleResId)) }, text = { Text(stringResource(page.titleResId)) },

View File

@ -4,12 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -17,13 +12,7 @@ import androidx.compose.material.icons.automirrored.outlined.OpenInNew
import androidx.compose.material.icons.outlined.FileDownload import androidx.compose.material.icons.outlined.FileDownload
import androidx.compose.material.icons.outlined.PostAdd import androidx.compose.material.icons.outlined.PostAdd
import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Save
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -42,6 +31,7 @@ import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.ui.component.AppScaffold import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.InstallerStatusDialog import app.revanced.manager.ui.component.InstallerStatusDialog
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.patcher.InstallPickerDialog import app.revanced.manager.ui.component.patcher.InstallPickerDialog
import app.revanced.manager.ui.component.patcher.Steps import app.revanced.manager.ui.component.patcher.Steps
import app.revanced.manager.ui.model.State import app.revanced.manager.ui.model.State
@ -121,7 +111,7 @@ fun PatcherScreen(
}, },
floatingActionButton = { floatingActionButton = {
AnimatedVisibility(visible = canInstall) { AnimatedVisibility(visible = canInstall) {
ExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { text = {
Text( Text(
stringResource(if (vm.installedPackageName == null) R.string.install_app else R.string.open_app) stringResource(if (vm.installedPackageName == null) R.string.install_app else R.string.open_app)

View File

@ -35,6 +35,9 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.LazyColumnWithScrollbar import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.SafeguardDialog import app.revanced.manager.ui.component.SafeguardDialog
import app.revanced.manager.ui.component.SearchView import app.revanced.manager.ui.component.SearchView
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticTab
import app.revanced.manager.ui.component.patches.OptionItem import app.revanced.manager.ui.component.patches.OptionItem
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_SUPPORTED import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_SUPPORTED
@ -293,7 +296,7 @@ fun PatchesSelectorScreen(
floatingActionButton = { floatingActionButton = {
if (!showPatchButton) return@Scaffold if (!showPatchButton) return@Scaffold
ExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.save)) }, text = { Text(stringResource(R.string.save)) },
icon = { icon = {
Icon( Icon(
@ -321,7 +324,7 @@ fun PatchesSelectorScreen(
containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp)
) { ) {
bundles.forEachIndexed { index, bundle -> bundles.forEachIndexed { index, bundle ->
Tab( HapticTab(
selected = pagerState.currentPage == index, selected = pagerState.currentPage == index,
onClick = { onClick = {
composableScope.launch { composableScope.launch {
@ -438,7 +441,7 @@ private fun PatchItem(
.clickable(onClick = onToggle) .clickable(onClick = onToggle)
.fillMaxSize(), .fillMaxSize(),
leadingContent = { leadingContent = {
Checkbox( HapticCheckbox(
checked = selected, checked = selected,
onCheckedChange = { onToggle() }, onCheckedChange = { onToggle() },
enabled = supported enabled = supported
@ -452,7 +455,7 @@ private fun PatchItem(
Icon(Icons.Outlined.Settings, null) Icon(Icons.Outlined.Settings, null)
} }
} }
} },
) )
@Composable @Composable

View File

@ -8,13 +8,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowRight import androidx.compose.material.icons.automirrored.outlined.ArrowRight
import androidx.compose.material.icons.filled.AutoFixHigh import androidx.compose.material.icons.filled.AutoFixHigh
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.*
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -28,6 +22,7 @@ import app.revanced.manager.R
import app.revanced.manager.ui.component.AppInfo import app.revanced.manager.ui.component.AppInfo
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.destination.SelectedAppInfoDestination import app.revanced.manager.ui.destination.SelectedAppInfoDestination
import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
@ -36,11 +31,7 @@ import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
import app.revanced.manager.util.Options import app.revanced.manager.util.Options
import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.PatchSelection
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
import dev.olshevski.navigation.reimagined.AnimatedNavHost import dev.olshevski.navigation.reimagined.*
import dev.olshevski.navigation.reimagined.NavBackHandler
import dev.olshevski.navigation.reimagined.navigate
import dev.olshevski.navigation.reimagined.pop
import dev.olshevski.navigation.reimagined.rememberNavController
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@ -161,7 +152,7 @@ private fun SelectedAppInfoScreen(
) )
}, },
floatingActionButton = { floatingActionButton = {
ExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.patch)) }, text = { Text(stringResource(R.string.patch)) },
icon = { icon = {
Icon( Icon(

View File

@ -11,10 +11,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -34,6 +32,8 @@ import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.LazyColumnWithScrollbar import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.ui.component.NonSuggestedVersionDialog import app.revanced.manager.ui.component.NonSuggestedVersionDialog
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.VersionSelectorViewModel import app.revanced.manager.ui.viewmodel.VersionSelectorViewModel
@ -81,7 +81,7 @@ fun VersionSelectorScreen(
) )
}, },
floatingActionButton = { floatingActionButton = {
ExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { Text(stringResource(R.string.select_version)) }, text = { Text(stringResource(R.string.select_version)) },
icon = { icon = {
Icon( Icon(
@ -170,7 +170,7 @@ fun SelectedAppItem(
alreadyPatched: Boolean = false, alreadyPatched: Boolean = false,
) { ) {
ListItem( ListItem(
leadingContent = { RadioButton(selected, null) }, leadingContent = { HapticRadioButton(selected, null) },
headlineContent = { Text(selectedApp.version) }, headlineContent = { Text(selectedApp.version) },
supportingContent = when (selectedApp) { supportingContent = when (selectedApp) {
is SelectedApp.Installed -> is SelectedApp.Installed ->

View File

@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -20,6 +19,7 @@ import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.DownloadsViewModel import app.revanced.manager.ui.viewmodel.DownloadsViewModel
@ -70,7 +70,7 @@ fun DownloadsSettingsScreen(
modifier = Modifier.clickable { viewModel.toggleItem(app) }, modifier = Modifier.clickable { viewModel.toggleItem(app) },
headlineContent = app.packageName, headlineContent = app.packageName,
leadingContent = (@Composable { leadingContent = (@Composable {
Checkbox( HapticCheckbox(
checked = selected, checked = selected,
onCheckedChange = { viewModel.toggleItem(app) } onCheckedChange = { viewModel.toggleItem(app) }
) )

View File

@ -28,6 +28,7 @@ import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.theme.Theme import app.revanced.manager.ui.theme.Theme
@ -113,7 +114,7 @@ private fun ThemePicker(
.clickable { selectedTheme = it }, .clickable { selectedTheme = it },
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
RadioButton( HapticRadioButton(
selected = selectedTheme == it, selected = selectedTheme == it,
onClick = { selectedTheme = it }) onClick = { selectedTheme = it })
Text(stringResource(it.displayName)) Text(stringResource(it.displayName))