diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt index 95ef5c0..79d453e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/Constants.kt @@ -26,4 +26,7 @@ object Constants { const val PREF_SORTING_METHOD = "sorting_method" const val PREF_SORTING_METHOD_DEFAULT = 1 + + const val PREF_HAPTIC_FEEDBACK = "haptic_feedback" + const val PREF_HAPTIC_FEEDBACK_DEFAULT = true } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt index 823b10c..aa4630c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constants/GlobalValues.kt @@ -27,4 +27,8 @@ object GlobalValues { key = Constants.PREF_SORTING_METHOD, default = Constants.PREF_SORTING_METHOD_DEFAULT ) + var hapticFeedback: Boolean by SPDelegates( + key = Constants.PREF_HAPTIC_FEEDBACK, + default = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt index b6f25ad..4776d3e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/ChipGroup.kt @@ -17,9 +17,11 @@ import androidx.compose.runtime.mutableIntStateOf 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.stringResource import androidx.compose.ui.unit.dp import cn.super12138.todo.R +import cn.super12138.todo.utils.VibrationUtils /** * 部分参考:https://github.com/Rhythamtech/FilterChipGroup-Compose-Android/blob/main/FilterChipGroup.kt @@ -32,6 +34,7 @@ fun FilterChipGroup( onSelectedChanged: (Int) -> Unit = {}, modifier: Modifier = Modifier ) { + val view = LocalView.current var selectedItemIndex by rememberSaveable { mutableIntStateOf(defaultSelectedItemIndex) } FlowRow(modifier = modifier) { @@ -40,6 +43,7 @@ fun FilterChipGroup( selected = items[selectedItemIndex] == items[index], onClick = { selectedItemIndex = index + VibrationUtils.performHapticFeedback(view) onSelectedChanged(index) }, leadingIcon = { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt index 313faca..3e278f4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Dialogs.kt @@ -10,8 +10,10 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import cn.super12138.todo.R +import cn.super12138.todo.utils.VibrationUtils @Composable fun WarningDialog( @@ -50,6 +52,7 @@ fun BasicDialog( onDismiss: () -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current BasicDialog( visible = visible, icon = { @@ -61,12 +64,22 @@ fun BasicDialog( title = { Text(title) }, text = text, confirmButton = { - FilledTonalButton(onClick = onConfirm) { + FilledTonalButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onConfirm() + } + ) { Text(confirmButton) } }, dismissButton = { - TextButton(onClick = onDismiss) { + TextButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onDismiss() + } + ) { Text(dismissButton) } }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt index 1102f2a..7460a3e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/FloatingActionButton.kt @@ -13,7 +13,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp +import cn.super12138.todo.utils.VibrationUtils /** * 带动画且比 Material 3 内置组件动画好看的的可扩展 FAB @@ -37,8 +39,12 @@ fun AnimatedExtendedFloatingActionButton( onClick: () -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current FloatingActionButton( - onClick = onClick, + onClick = { + VibrationUtils.performHapticFeedback(view) + onClick() + }, elevation = elevation, containerColor = containerColor, contentColor = contentColor, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt index 7f9ef93..12ece54 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/components/Scaffolds.kt @@ -21,9 +21,11 @@ import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import cn.super12138.todo.R +import cn.super12138.todo.utils.VibrationUtils /** * 带有顶部大标题栏的通用脚手架 @@ -47,6 +49,7 @@ fun LargeTopAppBarScaffold( modifier: Modifier = Modifier, content: @Composable (PaddingValues) -> Unit ) { + val view = LocalView.current LargeTopAppBarScaffold( title = { Text( @@ -56,7 +59,12 @@ fun LargeTopAppBarScaffold( ) }, navigationIcon = { - IconButton(onClick = onBack) { + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onBack() + } + ) { Icon( imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = stringResource(R.string.action_back) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt index 212d63f..920ba0b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextOverflow @@ -32,6 +33,7 @@ import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.ui.TodoDefaults import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton +import cn.super12138.todo.utils.VibrationUtils @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -40,6 +42,7 @@ fun CrashPage( exitApp: () -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val scrollState = rememberScrollState() val isExpanded by remember { @@ -66,7 +69,10 @@ fun CrashPage( }, floatingActionButton = { AnimatedExtendedFloatingActionButton( - onClick = exitApp, + onClick = { + VibrationUtils.performHapticFeedback(view) + exitApp() + }, icon = { Icon( imageVector = Icons.AutoMirrored.Outlined.ExitToApp, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt index c323d8c..aa51cad 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/editor/TodoEditorPage.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.editor +import android.view.HapticFeedbackConstants import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope @@ -42,6 +43,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics @@ -56,6 +58,7 @@ import cn.super12138.todo.ui.components.AnimatedExtendedFloatingActionButton import cn.super12138.todo.ui.components.FilterChipGroup import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.components.WarningDialog +import cn.super12138.todo.utils.VibrationUtils @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable @@ -71,6 +74,7 @@ fun TodoEditorPage( var showExitConfirmDialog by rememberSaveable { mutableStateOf(false) } var showDeleteConfirmDialog by rememberSaveable { mutableStateOf(false) } + val view = LocalView.current val context = LocalContext.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -220,7 +224,10 @@ fun TodoEditorPage( contentDescription = context.getString(R.string.label_priority) }, value = priorityState, - onValueChange = { priorityState = it }, + onValueChange = { + VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS) + priorityState = it + }, valueRange = -10f..10f, steps = 3, interactionSource = interactionSource, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt index 43e2bd8..447943b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/ManagerFragment.kt @@ -3,10 +3,7 @@ package cn.super12138.todo.ui.pages.main import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionScope -import androidx.compose.foundation.layout.Arrangement -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 @@ -15,7 +12,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource @@ -57,12 +53,12 @@ fun ManagerFragment( ) { if (list.isEmpty()) { item { - Text( - text = stringResource(R.string.tip_no_task), - style = MaterialTheme.typography.titleMedium, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) + Text( + text = stringResource(R.string.tip_no_task), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) } } else { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt index cc52891..79e5ed6 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoCard.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.main.components +import android.view.HapticFeedbackConstants import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.ExperimentalSharedTransitionApi @@ -32,6 +33,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow @@ -39,6 +41,7 @@ import androidx.compose.ui.unit.dp import cn.super12138.todo.R import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.model.Priority +import cn.super12138.todo.utils.VibrationUtils @OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class) @Composable @@ -56,6 +59,7 @@ fun TodoCard( animatedVisibilityScope: AnimatedVisibilityScope, modifier: Modifier = Modifier ) { + val view = LocalView.current val context = LocalContext.current ElevatedCard( modifier = modifier @@ -67,8 +71,17 @@ fun TodoCard( modifier = Modifier .fillMaxSize() .combinedClickable( - onClick = onCardClick, - onLongClick = onCardLongClick + onClick = { + VibrationUtils.performHapticFeedback(view) + onCardClick() + }, + onLongClick = { + VibrationUtils.performHapticFeedback( + view, + HapticFeedbackConstants.LONG_PRESS + ) + onCardLongClick() + } ) .padding(horizontal = 15.dp) ) { @@ -137,7 +150,12 @@ fun TodoCard( } AnimatedVisibility(!selected && !completed) { - IconButton(onClick = onChecked) { + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onChecked() + } + ) { Icon( imageVector = Icons.Outlined.Check, tint = MaterialTheme.colorScheme.primary, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt index ea4ffab..892e50b 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/main/components/TodoTopAppBar.kt @@ -27,9 +27,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import cn.super12138.todo.R +import cn.super12138.todo.utils.VibrationUtils @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -42,6 +44,7 @@ fun TodoTopAppBar( toSettingsPage: () -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current val animatedTopAppBarColors by animateColorAsState( targetValue = if (selectedMode) MaterialTheme.colorScheme.surfaceContainerHighest else MaterialTheme.colorScheme.surface ) @@ -49,7 +52,12 @@ fun TodoTopAppBar( TopAppBar( navigationIcon = { AnimatedVisibility(selectedMode) { - IconButton(onClick = onCancelSelect) { + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onCancelSelect() + } + ) { Icon( imageVector = Icons.Outlined.Close, contentDescription = stringResource(R.string.tip_clear_selected_items) @@ -73,14 +81,15 @@ fun TodoTopAppBar( AnimatedContent( targetState = selectedMode, modifier = Modifier.windowInsetsPadding( - WindowInsets.safeContent.exclude( - WindowInsets.ime - ) + WindowInsets.safeContent.exclude(WindowInsets.ime) ) ) { inSelectedMode -> if (!inSelectedMode) { IconButton( - onClick = toSettingsPage + onClick = { + VibrationUtils.performHapticFeedback(view) + toSettingsPage() + } ) { Icon( imageVector = Icons.Outlined.Settings, @@ -89,13 +98,23 @@ fun TodoTopAppBar( } } else { Row { - IconButton(onClick = onSelectAll) { + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onSelectAll() + } + ) { Icon( imageVector = Icons.Outlined.SelectAll, contentDescription = stringResource(R.string.tip_select_all) ) } - IconButton(onClick = onDeleteSelectedTodo) { + IconButton( + onClick = { + VibrationUtils.performHapticFeedback(view) + onDeleteSelectedTodo() + } + ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.action_delete) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt index ce8f713..7cc6ed9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsAbout.kt @@ -23,7 +23,7 @@ import cn.super12138.todo.constants.Constants import cn.super12138.todo.ui.components.LargeTopAppBarScaffold import cn.super12138.todo.ui.icons.GithubIcon import cn.super12138.todo.ui.pages.settings.components.SettingsItem -import cn.super12138.todo.utils.getAppVersion +import cn.super12138.todo.utils.SystemUtils @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -49,7 +49,7 @@ fun SettingsAbout( SettingsItem( leadingIcon = Icons.Outlined.Numbers, title = stringResource(R.string.pref_app_version), - description = getAppVersion(context) + description = SystemUtils.getAppVersion(context) ) SettingsItem( leadingIcon = Icons.Outlined.Person4, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt similarity index 86% rename from app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt rename to app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt index c3689ff..da47e9a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterface.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsInterfaceInteraction.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.Sort import androidx.compose.material.icons.outlined.Checklist +import androidx.compose.material.icons.outlined.Vibration import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -42,7 +43,7 @@ fun SettingsInterface( val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() var showSortingMethodDialog by rememberSaveable { mutableStateOf(false) } LargeTopAppBarScaffold( - title = stringResource(R.string.pref_interface), + title = stringResource(R.string.pref_interface_interaction), onBack = onNavigateUp, scrollBehavior = scrollBehavior, modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), @@ -70,6 +71,16 @@ fun SettingsInterface( description = viewModel.appSortingMethod.getDisplayName(context), onClick = { showSortingMethodDialog = true } ) + + SettingsCategory(stringResource(R.string.pref_category_global_interaction)) + SwitchSettingsItem( + key = Constants.PREF_HAPTIC_FEEDBACK, + default = Constants.PREF_HAPTIC_FEEDBACK_DEFAULT, + leadingIcon = Icons.Outlined.Vibration, + title = stringResource(R.string.pref_haptic_feedback), + description = stringResource(R.string.pref_haptic_feedback_desc), + onCheckedChange = {} + ) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt index 799fd94..25b349e 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/SettingsMain.kt @@ -47,8 +47,8 @@ fun SettingsMain( ) SettingsItem( leadingIcon = Icons.Outlined.ViewComfy, - title = stringResource(R.string.pref_interface), - description = stringResource(R.string.pref_interface_desc), + title = stringResource(R.string.pref_interface_interaction), + description = stringResource(R.string.pref_interface_interaction_desc), onClick = toInterfacePage ) SettingsItem( diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt index dda4819..1ce5b84 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsDialogs.kt @@ -15,10 +15,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp import cn.super12138.todo.ui.components.BasicDialog import cn.super12138.todo.ui.pages.settings.state.rememberPrefIntState +import cn.super12138.todo.utils.VibrationUtils @Composable fun SettingsRadioDialog( @@ -31,6 +33,7 @@ fun SettingsRadioDialog( onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { + val view = LocalView.current SettingsDialog( visible = visible, title = title, @@ -47,6 +50,7 @@ fun SettingsRadioDialog( selected = option.id == selectedItemIndex, onClick = { selectedItemIndex = option.id + VibrationUtils.performHapticFeedback(view) onSelect(option.id) onDismiss() }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt index 3b1bb74..8707a16 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SettingsItem.kt @@ -15,9 +15,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import cn.super12138.todo.utils.VibrationUtils @Composable fun SettingsItem( @@ -80,6 +82,7 @@ fun SettingsItem( onClick: () -> Unit = {}, modifier: Modifier = Modifier ) { + val view = LocalView.current Row( modifier = modifier .fillMaxWidth() @@ -87,7 +90,10 @@ fun SettingsItem( .clip(shape) .clickable( enabled = enableClick, - onClick = onClick // TODO: HapticFeedback + onClick = { + VibrationUtils.performHapticFeedback(view) + onClick() + } ) .padding(horizontal = 24.dp, vertical = 20.dp), verticalAlignment = Alignment.CenterVertically diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt index 6f819c6..3a31e33 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/SwitchSettingsItem.kt @@ -7,8 +7,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import cn.super12138.todo.ui.pages.settings.state.rememberPrefBooleanState +import cn.super12138.todo.utils.VibrationUtils @Composable fun SwitchSettingsItem( @@ -20,6 +22,7 @@ fun SwitchSettingsItem( onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current var switchState by rememberPrefBooleanState(key, default) SettingsItem( leadingIcon = leadingIcon, @@ -30,6 +33,7 @@ fun SwitchSettingsItem( checked = switchState, onCheckedChange = { switchState = it + VibrationUtils.performHapticFeedback(view) onCheckedChange(it) }, modifier = Modifier.padding(start = 14.dp) diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/contrast/ContrastPicker.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/contrast/ContrastPicker.kt index 4d0047f..ad31ab9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/contrast/ContrastPicker.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/contrast/ContrastPicker.kt @@ -1,5 +1,6 @@ package cn.super12138.todo.ui.pages.settings.components.contrast +import android.view.HapticFeedbackConstants import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -15,6 +16,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics @@ -24,12 +26,14 @@ import cn.super12138.todo.constants.Constants import cn.super12138.todo.logic.model.ContrastLevel import cn.super12138.todo.ui.pages.settings.components.MoreContentSettingsItem import cn.super12138.todo.ui.pages.settings.state.rememberPrefFloatState +import cn.super12138.todo.utils.VibrationUtils @Composable fun ContrastPicker( onContrastChange: (ContrastLevel) -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current val context = LocalContext.current MoreContentSettingsItem( title = stringResource(R.string.pref_contrast_level), @@ -46,6 +50,7 @@ fun ContrastPicker( }, value = contrastState, onValueChange = { + VibrationUtils.performHapticFeedback(view, HapticFeedbackConstants.LONG_PRESS) contrastState = it onContrastChange(ContrastLevel.fromFloat(it)) }, diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt index 9cea4c0..fa76169 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/darkmode/DarkModeItem.kt @@ -19,7 +19,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp +import cn.super12138.todo.utils.VibrationUtils @Composable fun DarkModeItem( @@ -31,11 +33,15 @@ fun DarkModeItem( onSelect: () -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current val borderWidth by animateDpAsState(if (selected) 3.dp else (-1).dp) Column( modifier = modifier .clip(MaterialTheme.shapes.large) - .clickable { onSelect() } + .clickable { + VibrationUtils.performHapticFeedback(view) + onSelect() + } .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/palette/PaletteItem.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/palette/PaletteItem.kt index 23219f0..f60529c 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/palette/PaletteItem.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/settings/components/palette/PaletteItem.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach @@ -31,6 +32,7 @@ import cn.super12138.todo.constants.GlobalValues import cn.super12138.todo.logic.model.ContrastLevel import cn.super12138.todo.ui.theme.PaletteStyle import cn.super12138.todo.ui.theme.dynamicColorScheme +import cn.super12138.todo.utils.VibrationUtils @Composable fun PaletteItem( @@ -40,12 +42,14 @@ fun PaletteItem( onSelect: () -> Unit, modifier: Modifier = Modifier ) { + val view = LocalView.current val context = LocalContext.current Column( modifier = Modifier .width(90.dp) .clip(MaterialTheme.shapes.large) .clickable { + VibrationUtils.performHapticFeedback(view) onSelect() } .padding(8.dp), diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt b/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt index 57e5604..6389357 100644 --- a/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt +++ b/app/src/main/kotlin/cn/super12138/todo/ui/theme/ThemeExt.kt @@ -2,7 +2,6 @@ package cn.super12138.todo.ui.theme import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.AnimationSpec -import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.material3.ColorScheme import androidx.compose.runtime.Composable diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt index 430c9f0..63401a4 100644 --- a/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt +++ b/app/src/main/kotlin/cn/super12138/todo/utils/SystemUtils.kt @@ -3,17 +3,19 @@ package cn.super12138.todo.utils import android.content.Context import android.os.Build -/** - * 获取应用版本号 - * @return 版本名称(版本代码) - */ -fun getAppVersion(context: Context): String { - val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0) - val verName = pkgInfo.versionName - val verCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - pkgInfo.longVersionCode.toInt() - } else { - pkgInfo.versionCode +object SystemUtils { + /** + * 获取应用版本号 + * @return 版本名称(版本代码) + */ + fun getAppVersion(context: Context): String { + val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0) + val verName = pkgInfo.versionName + val verCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + pkgInfo.longVersionCode.toInt() + } else { + pkgInfo.versionCode + } + return "$verName ($verCode)" } - return "$verName ($verCode)" } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt new file mode 100644 index 0000000..f20feba --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/utils/VibrationUtils.kt @@ -0,0 +1,16 @@ +package cn.super12138.todo.utils + +import android.view.HapticFeedbackConstants +import android.view.View +import cn.super12138.todo.constants.GlobalValues + +object VibrationUtils { + fun performHapticFeedback( + view: View, + feedbackConstants: Int = HapticFeedbackConstants.CONTEXT_CLICK + ) { + if (GlobalValues.hapticFeedback) { + view.performHapticFeedback(feedbackConstants) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9d6ee8c..97f9488 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -74,8 +74,8 @@ 极高 更改颜色对比度 - 界面 - 待办列表、排序方式 + 界面与交互 + 待办列表、触感反馈 显示已完成待办 在待办列表里显示已完成的待办 待办列表 @@ -89,4 +89,7 @@ 首字母(升序) 首字母(降序) 排序方式 + 全局交互 + 触感反馈 + 为某些操作提供轻微的震动反馈(须先在系统设置开启) \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6adbc49..399ba84 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,8 +75,8 @@ High Very High Change the contrast level - Interface - To-Do List, sorting method + Interface & Interaction + To-Do List, haptic feedback Show Completed Tasks Show completed tasks in to-do list To-Do List @@ -90,4 +90,7 @@ Alphabetical (Ascending) Alphabetical (Descending) Sorting Method + Global Interaction + Haptic Feedback + Provide slight vibration feedback for some operations (must be enabled in system settings first) \ No newline at end of file