Added settings to control haptic feedback (#16)

This commit is contained in:
Sad Ellie 2023-01-28 19:00:46 +04:00
parent 31f4903d48
commit b95906b31f
8 changed files with 109 additions and 58 deletions

View File

@ -999,6 +999,8 @@
<string name="separator_setting">Separator</string>
<string name="output_format_setting">Output format</string>
<string name="unit_groups_setting">Unit groups</string>
<string name="enable_vibrations">Vibrations</string>
<string name="enable_vibrations_support">Haptic feedback when clicking keyboard buttons</string>
<string name="currency_rates_note_setting">Wrong currency rates?</string>
<string name="currency_rates_note_title">Note</string>
<string name="currency_rates_note_text">Currency rates are updated daily. There\'s no real-time market monitoring in the app</string>

View File

@ -50,7 +50,8 @@ import javax.inject.Inject
* @property outputFormat Current [OutputFormat] that is applied to converted value (not input)
* @property latestLeftSideUnit Latest [AbstractUnit] that was on the left side
* @property latestRightSideUnit Latest [AbstractUnit] that was on the right side
* @property shownUnitGroups [UnitGroup]s that user wants to see. Excludes other [UnitGroup]s
* @property shownUnitGroups [UnitGroup]s that user wants to see. Excludes other [UnitGroup]s,
* @property enableVibrations When true will use haptic feedback in app.
*/
data class UserPreferences(
val themingMode: ThemingMode? = null,
@ -61,7 +62,8 @@ data class UserPreferences(
val outputFormat: Int = OutputFormat.PLAIN,
val latestLeftSideUnit: String = MyUnitIDS.kilometer,
val latestRightSideUnit: String = MyUnitIDS.mile,
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS
val shownUnitGroups: List<UnitGroup> = ALL_UNIT_GROUPS,
val enableVibrations: Boolean = true
)
/**
@ -82,6 +84,7 @@ UserPreferencesRepository @Inject constructor(private val dataStore: DataStore<P
val LATEST_LEFT_SIDE = stringPreferencesKey("LATEST_LEFT_SIDE_PREF_KEY")
val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY")
val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY")
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
}
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
@ -123,6 +126,7 @@ UserPreferencesRepository @Inject constructor(private val dataStore: DataStore<P
}
} ?: ALL_UNIT_GROUPS
val enableVibrations: Boolean = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true
UserPreferences(
themingMode = themingMode,
@ -133,7 +137,8 @@ UserPreferencesRepository @Inject constructor(private val dataStore: DataStore<P
outputFormat = outputFormat,
latestLeftSideUnit = latestLeftSideUnit,
latestRightSideUnit = latestRightSideUnit,
shownUnitGroups = shownUnitGroups
shownUnitGroups = shownUnitGroups,
enableVibrations = enableVibrations
)
}
@ -222,4 +227,15 @@ UserPreferencesRepository @Inject constructor(private val dataStore: DataStore<P
preferences[PrefsKeys.SHOWN_UNIT_GROUPS] = shownUnitGroups.joinToString(",")
}
}
/**
* Update preference on whether or not use haptic feedback.
*
* @param enabled True if user wants to enable this feature.
*/
suspend fun updateVibrations(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.ENABLE_VIBRATIONS] = enabled
}
}
}

View File

@ -53,6 +53,7 @@ internal fun MainScreen(
) {
var launched: Boolean by rememberSaveable { mutableStateOf(false) }
val mainScreenUIState = viewModel.uiStateFlow.collectAsStateWithLifecycle()
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
Scaffold(
modifier = Modifier,
@ -83,7 +84,8 @@ internal fun MainScreen(
processInput = { viewModel.processInput(it) },
deleteDigit = { viewModel.deleteDigit() },
clearInput = { viewModel.clearInput() },
onOutputTextFieldClick = { viewModel.toggleFormatTime() }
onOutputTextFieldClick = { viewModel.toggleFormatTime() },
allowVibration = userPrefs.value.enableVibrations
)
}
)
@ -109,7 +111,8 @@ private fun MainScreenContent(
processInput: (String) -> Unit = {},
deleteDigit: () -> Unit = {},
clearInput: () -> Unit = {},
onOutputTextFieldClick: () -> Unit
onOutputTextFieldClick: () -> Unit,
allowVibration: Boolean
) {
PortraitLandscape(
modifier = modifier,
@ -138,6 +141,7 @@ private fun MainScreenContent(
deleteDigit = deleteDigit,
clearInput = clearInput,
converterMode = mainScreenUIState.mode,
allowVibration = allowVibration
)
}
)

View File

@ -84,7 +84,7 @@ class MainViewModel @Inject constructor(
private val allUnitsRepository: AllUnitsRepository
) : ViewModel() {
private val _userPrefs = userPrefsRepository.userPreferencesFlow.stateIn(
val userPrefs = userPrefsRepository.userPreferencesFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
UserPreferences()
@ -444,7 +444,7 @@ class MainViewModel @Inject constructor(
// Now we evaluate expression in input
val evaluationResult: BigDecimal = try {
Expressions().eval(cleanInput)
.setScale(_userPrefs.value.digitsPrecision, RoundingMode.HALF_EVEN)
.setScale(userPrefs.value.digitsPrecision, RoundingMode.HALF_EVEN)
.trimZeros()
} catch (e: Exception) {
when (e) {
@ -470,9 +470,9 @@ class MainViewModel @Inject constructor(
} else {
_calculated.update {
evaluationResult
.setMinimumRequiredScale(_userPrefs.value.digitsPrecision)
.setMinimumRequiredScale(userPrefs.value.digitsPrecision)
.trimZeros()
.toStringWith(_userPrefs.value.outputFormat)
.toStringWith(userPrefs.value.outputFormat)
}
}
@ -481,11 +481,11 @@ class MainViewModel @Inject constructor(
val conversionResult: BigDecimal = unitFrom.convert(
unitTo,
evaluationResult,
_userPrefs.value.digitsPrecision
userPrefs.value.digitsPrecision
)
// Converted
_result.update { conversionResult.toStringWith(_userPrefs.value.outputFormat) }
_result.update { conversionResult.toStringWith(userPrefs.value.outputFormat) }
}
private fun setInputSymbols(symbol: String, add: Boolean = true) {
@ -564,7 +564,7 @@ class MainViewModel @Inject constructor(
private fun startObserving() {
viewModelScope.launch(Dispatchers.Default) {
merge(_input, _unitFrom, _unitTo, _showLoading, _userPrefs).collectLatest {
merge(_input, _unitFrom, _unitTo, _showLoading, userPrefs).collectLatest {
convertInput()
}
}

View File

@ -74,11 +74,12 @@ internal fun Keyboard(
deleteDigit: () -> Unit = {},
clearInput: () -> Unit = {},
converterMode: ConverterMode,
allowVibration: Boolean
) {
Crossfade(converterMode, modifier = modifier) {
when (it) {
ConverterMode.DEFAULT -> DefaultKeyboard(addDigit, clearInput, deleteDigit)
ConverterMode.BASE -> BaseKeyboard(addDigit, clearInput, deleteDigit)
ConverterMode.DEFAULT -> DefaultKeyboard(addDigit, clearInput, deleteDigit, allowVibration)
ConverterMode.BASE -> BaseKeyboard(addDigit, clearInput, deleteDigit, allowVibration)
}
}
@ -88,7 +89,8 @@ internal fun Keyboard(
private fun DefaultKeyboard(
addDigit: (String) -> Unit,
clearInput: () -> Unit,
deleteDigit: () -> Unit
deleteDigit: () -> Unit,
allowVibration: Boolean
) {
Column {
// Button modifier
@ -99,38 +101,34 @@ private fun DefaultKeyboard(
// Column modifier
val cModifier = Modifier.weight(1f)
Row(cModifier) {
KeyboardButton(bModifier, KEY_LEFT_BRACKET, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_RIGHT_BRACKET, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_EXPONENT, isPrimary = false, onClick = { addDigit(
KEY_EXPONENT
) })
KeyboardButton(bModifier, KEY_SQRT, isPrimary = false, onClick = { addDigit(KEY_SQRT) })
KeyboardButton(bModifier, KEY_LEFT_BRACKET, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_RIGHT_BRACKET, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_EXPONENT, isPrimary = false, allowVibration = allowVibration, onClick = { addDigit(KEY_EXPONENT) })
KeyboardButton(bModifier, KEY_SQRT, isPrimary = false, allowVibration = allowVibration, onClick = { addDigit(KEY_SQRT) })
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_7, onClick = addDigit)
KeyboardButton(bModifier, KEY_8, onClick = addDigit)
KeyboardButton(bModifier, KEY_9, onClick = addDigit)
KeyboardButton(bModifier, KEY_DIVIDE_DISPLAY, isPrimary = false) { addDigit(KEY_DIVIDE) }
KeyboardButton(bModifier, KEY_7, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_8, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_9, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_DIVIDE_DISPLAY, isPrimary = false, allowVibration = allowVibration) { addDigit(KEY_DIVIDE) }
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_4, onClick = addDigit)
KeyboardButton(bModifier, KEY_5, onClick = addDigit)
KeyboardButton(bModifier, KEY_6, onClick = addDigit)
KeyboardButton(bModifier, KEY_MULTIPLY_DISPLAY, isPrimary = false) { addDigit(
KEY_MULTIPLY
) }
KeyboardButton(bModifier, KEY_4, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_5, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_6, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_MULTIPLY_DISPLAY, isPrimary = false, allowVibration = allowVibration, ) { addDigit(KEY_MULTIPLY) }
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_1, onClick = addDigit)
KeyboardButton(bModifier, KEY_2, onClick = addDigit)
KeyboardButton(bModifier, KEY_3, onClick = addDigit)
KeyboardButton(bModifier, KEY_MINUS_DISPLAY, isPrimary = false) { addDigit(KEY_MINUS) }
KeyboardButton(bModifier, KEY_1, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_2, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_3, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_MINUS_DISPLAY, isPrimary = false, allowVibration = allowVibration) { addDigit(KEY_MINUS) }
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_0, onClick = addDigit)
KeyboardButton(bModifier, Formatter.fractional) { addDigit(KEY_DOT) }
KeyboardButton(bModifier, KEY_CLEAR, onLongClick = clearInput) { deleteDigit() }
KeyboardButton(bModifier, KEY_PLUS, isPrimary = false) { addDigit(KEY_PLUS) }
KeyboardButton(bModifier, KEY_0, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, Formatter.fractional, allowVibration = allowVibration, ) { addDigit(KEY_DOT) }
KeyboardButton(bModifier, KEY_CLEAR, allowVibration = allowVibration, onLongClick = clearInput) { deleteDigit() }
KeyboardButton(bModifier, KEY_PLUS, isPrimary = false, allowVibration = allowVibration, ) { addDigit(KEY_PLUS) }
}
}
}
@ -139,7 +137,8 @@ private fun DefaultKeyboard(
private fun BaseKeyboard(
addDigit: (String) -> Unit,
clearInput: () -> Unit,
deleteDigit: () -> Unit
deleteDigit: () -> Unit,
allowVibration: Boolean
) {
Column {
// Button modifier
@ -150,38 +149,39 @@ private fun BaseKeyboard(
// Column modifier
val cModifier = Modifier.weight(1f)
Row(cModifier) {
KeyboardButton(bModifier, KEY_BASE_A, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_B, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_C, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_A, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_B, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_C, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_BASE_D, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_E, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_F, isPrimary = false, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_D, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_E, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_BASE_F, isPrimary = false, allowVibration = allowVibration, onClick = addDigit)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_7, onClick = addDigit)
KeyboardButton(bModifier, KEY_8, onClick = addDigit)
KeyboardButton(bModifier, KEY_9, onClick = addDigit)
KeyboardButton(bModifier, KEY_7, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_8, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_9, allowVibration = allowVibration, onClick = addDigit)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_4, onClick = addDigit)
KeyboardButton(bModifier, KEY_5, onClick = addDigit)
KeyboardButton(bModifier, KEY_6, onClick = addDigit)
KeyboardButton(bModifier, KEY_4, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_5, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_6, allowVibration = allowVibration, onClick = addDigit)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_1, onClick = addDigit)
KeyboardButton(bModifier, KEY_2, onClick = addDigit)
KeyboardButton(bModifier, KEY_3, onClick = addDigit)
KeyboardButton(bModifier, KEY_1, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_2, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(bModifier, KEY_3, allowVibration = allowVibration, onClick = addDigit)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_0, onClick = addDigit)
KeyboardButton(bModifier, KEY_0, allowVibration = allowVibration, onClick = addDigit)
KeyboardButton(
Modifier
.fillMaxSize()
.weight(2f)
.padding(4.dp),
KEY_CLEAR,
allowVibration = allowVibration,
onLongClick = clearInput
) { deleteDigit() }
}

View File

@ -44,6 +44,7 @@ import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge
*
* @param modifier Modifier that is applied to a [Button] component.
* @param digit Symbol to show on button.
* @param allowVibration When true will vibrate on button press.
* @param isPrimary If true will use `inverseOnSurface` color, else `secondaryContainer`. Primary
* buttons are digits.
* @param onLongClick Action to perform when holding this button.
@ -53,6 +54,7 @@ import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge
internal fun KeyboardButton(
modifier: Modifier = Modifier,
digit: String,
allowVibration: Boolean,
isPrimary: Boolean = true,
onLongClick: (() -> Unit)? = null,
onClick: (String) -> Unit = {}
@ -83,6 +85,6 @@ internal fun KeyboardButton(
}
LaunchedEffect(key1 = isPressed) {
if (isPressed) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
if (isPressed and allowVibration) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
}
}

View File

@ -26,6 +26,7 @@ import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.RateReview
import androidx.compose.material.icons.filled.Rule
import androidx.compose.material.icons.filled.Vibration
import androidx.compose.material.icons.filled._123
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
@ -44,10 +45,11 @@ import com.sadellie.unitto.core.base.BuildConfig
import com.sadellie.unitto.core.base.OUTPUT_FORMAT
import com.sadellie.unitto.core.base.PRECISIONS
import com.sadellie.unitto.core.base.SEPARATORS
import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.openLink
import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.feature.settings.components.AlertDialogWithList
import com.sadellie.unitto.feature.settings.navigation.aboutRoute
import com.sadellie.unitto.feature.settings.navigation.themesRoute
@ -144,6 +146,22 @@ internal fun SettingsScreen(
// ADDITIONAL GROUP
item { Header(stringResource(R.string.additional_settings_group)) }
// VIBRATIONS
item {
UnittoListItem(
label = stringResource(R.string.enable_vibrations),
leadingContent = {
Icon(
Icons.Default.Vibration,
stringResource(R.string.enable_vibrations)
)
},
supportText = stringResource(R.string.enable_vibrations_support),
switchState = userPrefs.value.enableVibrations,
onSwitchChange = viewModel::updateVibrations
)
}
// RATE THIS APP
if (BuildConfig.STORE_LINK.isNotEmpty()) {
item {

View File

@ -100,6 +100,15 @@ class SettingsViewModel @Inject constructor(
}
}
/**
* See [UserPreferencesRepository.updateVibrations]
*/
fun updateVibrations(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateVibrations(enabled)
}
}
/**
* See [UnitGroupsRepository.markUnitGroupAsHidden] and
* [UserPreferencesRepository.updateShownUnitGroups]