From 85d0cc5f55a6aa0e423962d27cac27c2995f62cb Mon Sep 17 00:00:00 2001 From: Mike Trewartha Date: Sat, 6 Jan 2024 15:59:48 -0600 Subject: [PATCH 1/2] Vibrate when the compass crosses north --- .../positional/ui/compass/Compass.kt | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/compass/Compass.kt b/app/src/main/kotlin/io/trewartha/positional/ui/compass/Compass.kt index 3f28f343..02aa3965 100644 --- a/app/src/main/kotlin/io/trewartha/positional/ui/compass/Compass.kt +++ b/app/src/main/kotlin/io/trewartha/positional/ui/compass/Compass.kt @@ -12,19 +12,24 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import com.google.android.material.color.MaterialColors.harmonize @@ -47,6 +52,7 @@ fun Compass( contentAlignment = Alignment.Center ) { if (azimuth != null) { + NorthHapticFeedback(azimuth) CompassReading(azimuth) CompassRose(azimuth, Modifier.fillMaxSize()) } @@ -276,7 +282,25 @@ private fun DirectionText(azimuth: Angle, modifier: Modifier = Modifier) { } } +@Composable +private fun NorthHapticFeedback(azimuth: Angle) { + var previousQuadrant by remember { mutableStateOf(null) } + val currentQuadrant by remember(azimuth) { derivedStateOf { azimuth.quadrant } } + val crossedNorth = previousQuadrant != null && + ((previousQuadrant == Quadrant.NW && currentQuadrant == Quadrant.NE) || + (previousQuadrant == Quadrant.NE && currentQuadrant == Quadrant.NW)) + previousQuadrant = currentQuadrant + val hapticFeedback = LocalHapticFeedback.current + LaunchedEffect(crossedNorth) { + if (crossedNorth) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + } +} + private const val AZIMUTH_DEFAULT = 0f +private const val AZIMUTH_N = 0f +private const val AZIMUTH_E = 90f +private const val AZIMUTH_S = 180f +private const val AZIMUTH_W = 270f private const val AZIMUTH_N_MIN = 337.5f private const val AZIMUTH_N_MAX = 22.5f private const val AZIMUTH_E_MIN = 67.5f @@ -310,16 +334,21 @@ private val TICK_MAJOR_LENGTH = 16.dp private val TICK_MINOR_WIDTH = 8.dp private val TICK_MINOR_LENGTH = 8.dp -private data class TickStyle( - val color: Color, - val lengthPx: Float, - val widthPx: Float -) +private enum class Quadrant { NE, SE, SW, NW } + +private data class TickStyle(val color: Color, val lengthPx: Float, val widthPx: Float) + +private val Angle.quadrant: Quadrant + get() = when (inDegrees().value) { + in AZIMUTH_N..AZIMUTH_E -> Quadrant.NE + in AZIMUTH_E..AZIMUTH_S -> Quadrant.SE + in AZIMUTH_S..AZIMUTH_W -> Quadrant.SW + else -> Quadrant.NW + } private fun Float.toRadians(): Float = (this / DEGREES_180 * Math.PI).toFloat() @PreviewLightDark -@PreviewScreenSizes @Composable private fun CompassPreview() { PositionalTheme { From 971a7e230a22136b6a1d35b4c2c2cdb7ca22da4d Mon Sep 17 00:00:00 2001 From: Mike Trewartha Date: Sat, 6 Jan 2024 22:59:38 -0600 Subject: [PATCH 2/2] Add setting for compass north vibration --- app/src/main/AndroidManifest.xml | 2 + .../positional/ui/compass/Compass.kt | 18 +++++--- .../positional/ui/compass/CompassView.kt | 34 ++++++++++----- .../positional/ui/compass/CompassViewModel.kt | 15 ++++--- .../positional/ui/locals/LocalVibrator.kt | 8 ++++ .../settings/CompassNorthVibrationSetting.kt | 41 ++++++++++++++++++ .../positional/ui/settings/SettingsView.kt | 15 +++++++ .../ui/settings/SettingsViewModel.kt | 9 ++++ app/src/main/res/values/strings.xml | 7 +++ .../settings/DataStoreSettingsRepository.kt | 37 ++++++++++++++++ .../data/settings/SettingsRepository.kt | 13 ++++++ .../data/ui/CompassNorthVibration.kt | 43 +++++++++++++++++++ .../main/proto/compass_north_vibration.proto | 12 ++++++ data/src/main/proto/settings.proto | 2 + .../DataStoreSettingsRepositoryTest.kt | 26 +++++++++++ 15 files changed, 259 insertions(+), 23 deletions(-) create mode 100644 app/src/main/kotlin/io/trewartha/positional/ui/locals/LocalVibrator.kt create mode 100644 app/src/main/kotlin/io/trewartha/positional/ui/settings/CompassNorthVibrationSetting.kt create mode 100644 data/src/main/java/io/trewartha/positional/data/ui/CompassNorthVibration.kt create mode 100644 data/src/main/proto/compass_north_vibration.proto diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8ce5f88b..50c76ae3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ android:name="android.hardware.sensor.compass" android:required="false" /> + + (null) } val currentQuadrant by remember(azimuth) { derivedStateOf { azimuth.quadrant } } val crossedNorth = previousQuadrant != null && ((previousQuadrant == Quadrant.NW && currentQuadrant == Quadrant.NE) || (previousQuadrant == Quadrant.NE && currentQuadrant == Quadrant.NW)) previousQuadrant = currentQuadrant - val hapticFeedback = LocalHapticFeedback.current + val vibrator = LocalVibrator.current LaunchedEffect(crossedNorth) { - if (crossedNorth) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + if (crossedNorth && northVibration != null) { + @Suppress("DEPRECATION") // It matches our needs and goes back pre API 21 + vibrator.vibrate(northVibration.duration.inWholeMilliseconds) + } } } @@ -353,7 +357,7 @@ private fun Float.toRadians(): Float = (this / DEGREES_180 * Math.PI).toFloat() private fun CompassPreview() { PositionalTheme { Surface(Modifier.size(600.dp, 300.dp)) { - Compass(azimuth = Angle.Degrees(25f)) + Compass(azimuth = Angle.Degrees(25f), northVibration = CompassNorthVibration.SHORT) } } } diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassView.kt b/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassView.kt index 48664b9c..bfc38e64 100644 --- a/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassView.kt +++ b/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassView.kt @@ -1,6 +1,7 @@ package io.trewartha.positional.ui.compass import android.content.Context +import android.os.Vibrator import android.view.Surface import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -29,6 +30,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -54,6 +56,7 @@ import io.trewartha.positional.data.compass.CompassAccuracy import io.trewartha.positional.data.compass.CompassAzimuth import io.trewartha.positional.data.compass.CompassMode import io.trewartha.positional.data.measurement.Angle +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.domain.compass.CompassReading import io.trewartha.positional.ui.NavDestination import io.trewartha.positional.ui.PositionalTheme @@ -61,6 +64,7 @@ import io.trewartha.positional.ui.bottomNavEnterTransition import io.trewartha.positional.ui.bottomNavExitTransition import io.trewartha.positional.ui.bottomNavPopEnterTransition import io.trewartha.positional.ui.bottomNavPopExitTransition +import io.trewartha.positional.ui.locals.LocalVibrator import io.trewartha.positional.ui.utils.placeholder fun NavGraphBuilder.compassView(navController: NavController, contentPadding: PaddingValues) { @@ -73,10 +77,16 @@ fun NavGraphBuilder.compassView(navController: NavController, contentPadding: Pa ) { val viewModel: CompassViewModel = hiltViewModel() val state by viewModel.state.collectAsStateWithLifecycle() - CompassView( - state = state, - contentPadding = contentPadding, - onHelpClick = { navController.navigate(NavDestination.CompassHelp.route) }) + CompositionLocalProvider( + LocalVibrator provides + @Suppress("DEPRECATION") // It matches our needs and goes back pre API 21 + LocalContext.current.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + ) { + CompassView( + state = state, + contentPadding = contentPadding, + onHelpClick = { navController.navigate(NavDestination.CompassHelp.route) }) + } } } @@ -180,14 +190,17 @@ private fun SensorsPresentContent( } val context = LocalContext.current val baseAzimuth = (state as? CompassViewModel.State.SensorsPresent.Loaded)?.let { - when (it.compassMode) { - CompassMode.MAGNETIC_NORTH -> it.compassReading.magneticAzimuth - CompassMode.TRUE_NORTH -> it.compassReading.trueAzimuth + when (it.mode) { + CompassMode.MAGNETIC_NORTH -> it.reading.magneticAzimuth + CompassMode.TRUE_NORTH -> it.reading.trueAzimuth }?.angle } val adjustedAzimuth = baseAzimuth?.let { adjustAzimuthForDisplayRotation(context, it) } + val northVibration = + (state as? CompassViewModel.State.SensorsPresent.Loaded)?.northVibration Compass( adjustedAzimuth, + northVibration, Modifier .sizeIn(maxWidth = 480.dp, maxHeight = 480.dp) .weight(1f) @@ -197,7 +210,7 @@ private fun SensorsPresentContent( verticalAlignment = Alignment.CenterVertically ) { val declination = (state as? CompassViewModel.State.SensorsPresent.Loaded) - ?.compassReading?.magneticDeclination?.inDegrees()?.value + ?.reading?.magneticDeclination?.inDegrees()?.value DeclinationText(declination) HelpButton(onHelpClick) } @@ -290,7 +303,7 @@ private fun SensorsPresentLoadedPreview() { Surface { CompassView( state = CompassViewModel.State.SensorsPresent.Loaded( - compassReading = CompassReading( + reading = CompassReading( magneticAzimuth = CompassAzimuth( angle = Angle.Degrees(40f), accelerometerAccuracy = CompassAccuracy.HIGH, @@ -298,7 +311,8 @@ private fun SensorsPresentLoadedPreview() { ), magneticDeclination = Angle.Degrees(5f) ), - compassMode = CompassMode.TRUE_NORTH + mode = CompassMode.TRUE_NORTH, + northVibration = CompassNorthVibration.SHORT ), contentPadding = PaddingValues(), onHelpClick = {} diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassViewModel.kt b/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassViewModel.kt index d8a2b0fe..3535ac99 100644 --- a/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassViewModel.kt +++ b/app/src/main/kotlin/io/trewartha/positional/ui/compass/CompassViewModel.kt @@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import io.trewartha.positional.data.compass.CompassHardwareException import io.trewartha.positional.data.compass.CompassMode import io.trewartha.positional.data.settings.SettingsRepository +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.domain.compass.CompassReading import io.trewartha.positional.domain.compass.GetCompassReadingsUseCase import io.trewartha.positional.ui.utils.flow.ForViewModel @@ -23,11 +24,12 @@ class CompassViewModel @Inject constructor( ) : ViewModel() { val state: StateFlow = - combine( + combine( getCompassReadingsUseCase(), - settingsRepository.compassMode - ) { readings, mode -> - State.SensorsPresent.Loaded(readings, mode) + settingsRepository.compassMode, + settingsRepository.compassNorthVibration + ) { reading, mode, northVibration -> + State.SensorsPresent.Loaded(reading, mode, northVibration) }.catch { throwable -> when (throwable) { is CompassHardwareException -> { @@ -52,8 +54,9 @@ class CompassViewModel @Inject constructor( data object Loading : SensorsPresent data class Loaded( - val compassReading: CompassReading, - val compassMode: CompassMode + val reading: CompassReading, + val mode: CompassMode, + val northVibration: CompassNorthVibration ) : SensorsPresent } } diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/locals/LocalVibrator.kt b/app/src/main/kotlin/io/trewartha/positional/ui/locals/LocalVibrator.kt new file mode 100644 index 00000000..6a3af3cf --- /dev/null +++ b/app/src/main/kotlin/io/trewartha/positional/ui/locals/LocalVibrator.kt @@ -0,0 +1,8 @@ +package io.trewartha.positional.ui.locals + +import android.os.Vibrator +import androidx.compose.runtime.staticCompositionLocalOf + +val LocalVibrator = staticCompositionLocalOf { + error("No vibrator has been specified") +} diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/settings/CompassNorthVibrationSetting.kt b/app/src/main/kotlin/io/trewartha/positional/ui/settings/CompassNorthVibrationSetting.kt new file mode 100644 index 00000000..38c22f71 --- /dev/null +++ b/app/src/main/kotlin/io/trewartha/positional/ui/settings/CompassNorthVibrationSetting.kt @@ -0,0 +1,41 @@ +package io.trewartha.positional.ui.settings + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Vibration +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.trewartha.positional.R +import io.trewartha.positional.data.ui.CompassNorthVibration + +@Composable +fun CompassNorthVibrationSetting( + value: CompassNorthVibration?, + onValueChange: (CompassNorthVibration) -> Unit, + modifier: Modifier = Modifier +) { + ListSetting( + icon = Icons.Rounded.Vibration, + title = stringResource(R.string.settings_compass_north_vibration_title), + values = CompassNorthVibration.entries.toSet(), + value = value, + valueName = { compassNorthVibration -> + stringResource( + when (compassNorthVibration) { + CompassNorthVibration.NONE -> + R.string.settings_compass_north_vibration_value_none + CompassNorthVibration.SHORT -> + R.string.settings_compass_north_vibration_value_short + CompassNorthVibration.MEDIUM -> + R.string.settings_compass_north_vibration_value_medium + CompassNorthVibration.LONG -> + R.string.settings_compass_north_vibration_value_long + } + ) + }, + valuesDialogTitle = stringResource(R.string.settings_compass_north_vibration_dialog_title), + valuesDialogText = null, + onValueChange = onValueChange, + modifier = modifier + ) +} diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsView.kt b/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsView.kt index bbd42a46..2257ac4f 100644 --- a/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsView.kt +++ b/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsView.kt @@ -33,6 +33,7 @@ import io.trewartha.positional.R import io.trewartha.positional.data.compass.CompassMode import io.trewartha.positional.data.location.CoordinatesFormat import io.trewartha.positional.data.measurement.Units +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.data.ui.LocationAccuracyVisibility import io.trewartha.positional.data.ui.Theme import io.trewartha.positional.ui.NavDestination.Settings @@ -56,6 +57,7 @@ fun NavGraphBuilder.settingsView( ) { val viewModel: SettingsViewModel = hiltViewModel() val compassMode by viewModel.compassMode.collectAsStateWithLifecycle() + val compassNorthVibration by viewModel.compassNorthVibration.collectAsStateWithLifecycle() val coordinatesFormat by viewModel.coordinatesFormat.collectAsStateWithLifecycle() val locationAccuracyVisibility by viewModel.locationAccuracyVisibility .collectAsStateWithLifecycle() @@ -64,6 +66,8 @@ fun NavGraphBuilder.settingsView( SettingsView( compassMode = compassMode, onCompassModeChange = viewModel::onCompassModeChange, + compassNorthVibration = compassNorthVibration, + onCompassNorthVibrationChange = viewModel::onCompassNorthVibrationChange, coordinatesFormat = coordinatesFormat, onCoordinatesFormatChange = viewModel::onCoordinatesFormatChange, locationAccuracyVisibility = locationAccuracyVisibility, @@ -84,6 +88,8 @@ fun NavGraphBuilder.settingsView( private fun SettingsView( compassMode: CompassMode?, onCompassModeChange: (CompassMode) -> Unit, + compassNorthVibration: CompassNorthVibration?, + onCompassNorthVibrationChange: (CompassNorthVibration) -> Unit, coordinatesFormat: CoordinatesFormat?, onCoordinatesFormatChange: (CoordinatesFormat) -> Unit, locationAccuracyVisibility: LocationAccuracyVisibility?, @@ -139,6 +145,11 @@ private fun SettingsView( onValueChange = onCompassModeChange, modifier = Modifier.fillMaxWidth() ) + CompassNorthVibrationSetting( + value = compassNorthVibration, + onValueChange = onCompassNorthVibrationChange, + modifier = Modifier.fillMaxWidth() + ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { LocationAccuracyVisibilitySetting( value = locationAccuracyVisibility, @@ -173,6 +184,8 @@ private fun LoadingPreviews() { SettingsView( compassMode = null, onCompassModeChange = {}, + compassNorthVibration = null, + onCompassNorthVibrationChange = {}, coordinatesFormat = null, onCoordinatesFormatChange = {}, locationAccuracyVisibility = null, @@ -195,6 +208,8 @@ private fun LoadedPreviews() { SettingsView( compassMode = CompassMode.TRUE_NORTH, onCompassModeChange = {}, + compassNorthVibration = CompassNorthVibration.SHORT, + onCompassNorthVibrationChange = {}, coordinatesFormat = CoordinatesFormat.DD, onCoordinatesFormatChange = {}, locationAccuracyVisibility = LocationAccuracyVisibility.SHOW, diff --git a/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsViewModel.kt b/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsViewModel.kt index 052d3629..d19e5d76 100644 --- a/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsViewModel.kt +++ b/app/src/main/kotlin/io/trewartha/positional/ui/settings/SettingsViewModel.kt @@ -7,6 +7,7 @@ import io.trewartha.positional.data.compass.CompassMode import io.trewartha.positional.data.location.CoordinatesFormat import io.trewartha.positional.data.measurement.Units import io.trewartha.positional.data.settings.SettingsRepository +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.data.ui.LocationAccuracyVisibility import io.trewartha.positional.data.ui.Theme import io.trewartha.positional.ui.utils.flow.ForViewModel @@ -24,6 +25,10 @@ class SettingsViewModel @Inject constructor( val compassMode: StateFlow = settingsRepository.compassMode .stateIn(viewModelScope, SharingStarted.ForViewModel, initialValue = null) + val compassNorthVibration: StateFlow = + settingsRepository.compassNorthVibration + .stateIn(viewModelScope, SharingStarted.ForViewModel, initialValue = null) + val coordinatesFormat: StateFlow = settingsRepository.coordinatesFormat .stateIn(viewModelScope, SharingStarted.ForViewModel, initialValue = null) @@ -41,6 +46,10 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { settingsRepository.setCompassMode(compassMode) } } + fun onCompassNorthVibrationChange(compassNorthVibration: CompassNorthVibration) { + viewModelScope.launch { settingsRepository.setCompassNorthVibration(compassNorthVibration) } + } + fun onCoordinatesFormatChange(coordinatesFormat: CoordinatesFormat) { viewModelScope.launch { settingsRepository.setCoordinatesFormat(coordinatesFormat) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f5d499a4..bf7af7f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,6 +84,13 @@ Magnetic North True North + Compass north vibration + Compass north vibration + Long + Medium + None + Short + Coordinates format Coordinates format Decimal degrees diff --git a/data/src/main/java/io/trewartha/positional/data/settings/DataStoreSettingsRepository.kt b/data/src/main/java/io/trewartha/positional/data/settings/DataStoreSettingsRepository.kt index 6ab5ce0f..8190511c 100644 --- a/data/src/main/java/io/trewartha/positional/data/settings/DataStoreSettingsRepository.kt +++ b/data/src/main/java/io/trewartha/positional/data/settings/DataStoreSettingsRepository.kt @@ -12,6 +12,11 @@ import io.trewartha.positional.data.measurement.Units import io.trewartha.positional.data.settings.CompassModeProto.CompassMode.COMPASS_MODE_MAGNETIC_NORTH import io.trewartha.positional.data.settings.CompassModeProto.CompassMode.COMPASS_MODE_TRUE_NORTH import io.trewartha.positional.data.settings.CompassModeProto.CompassMode.COMPASS_MODE_UNSPECIFIED +import io.trewartha.positional.data.settings.CompassNorthVibrationProto.CompassNorthVibration.COMPASS_NORTH_VIBRATION_LONG +import io.trewartha.positional.data.settings.CompassNorthVibrationProto.CompassNorthVibration.COMPASS_NORTH_VIBRATION_MEDIUM +import io.trewartha.positional.data.settings.CompassNorthVibrationProto.CompassNorthVibration.COMPASS_NORTH_VIBRATION_NONE +import io.trewartha.positional.data.settings.CompassNorthVibrationProto.CompassNorthVibration.COMPASS_NORTH_VIBRATION_SHORT +import io.trewartha.positional.data.settings.CompassNorthVibrationProto.CompassNorthVibration.COMPASS_NORTH_VIBRATION_UNSPECIFIED import io.trewartha.positional.data.settings.CoordinatesFormatProto.CoordinatesFormat.COORDINATES_FORMAT_DECIMAL_DEGREES import io.trewartha.positional.data.settings.CoordinatesFormatProto.CoordinatesFormat.COORDINATES_FORMAT_DEGREES_DECIMAL_MINUTES import io.trewartha.positional.data.settings.CoordinatesFormatProto.CoordinatesFormat.COORDINATES_FORMAT_DEGREES_MINUTES_SECONDS @@ -30,6 +35,7 @@ import io.trewartha.positional.data.settings.ThemeProto.Theme.THEME_UNSPECIFIED import io.trewartha.positional.data.settings.UnitsProto.Units.UNITS_IMPERIAL import io.trewartha.positional.data.settings.UnitsProto.Units.UNITS_METRIC import io.trewartha.positional.data.settings.UnitsProto.Units.UNITS_UNSPECIFIED +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.data.ui.LocationAccuracyVisibility import io.trewartha.positional.data.ui.Theme import kotlinx.coroutines.flow.Flow @@ -53,6 +59,9 @@ class DataStoreSettingsRepository @Inject constructor( override val compassMode: Flow = context.settingsDataStore.data.map { it.compassMode.toData() } + override val compassNorthVibration: Flow = + context.settingsDataStore.data.map { it.compassNorthVibration.toData() } + override val coordinatesFormat: Flow = context.settingsDataStore.data.map { it.coordinatesFormat.toData() } @@ -71,6 +80,12 @@ class DataStoreSettingsRepository @Inject constructor( } } + override suspend fun setCompassNorthVibration(compassNorthVibration: CompassNorthVibration) { + context.settingsDataStore.updateData { + it.toBuilder().setCompassNorthVibration(compassNorthVibration.toProto()).build() + } + } + override suspend fun setCoordinatesFormat(coordinatesFormat: CoordinatesFormat) { context.settingsDataStore.updateData { it.toBuilder().setCoordinatesFormat(coordinatesFormat.toProto()).build() @@ -109,6 +124,28 @@ class DataStoreSettingsRepository @Inject constructor( COMPASS_MODE_MAGNETIC_NORTH -> CompassMode.MAGNETIC_NORTH } + private fun CompassNorthVibration.toProto(): CompassNorthVibrationProto.CompassNorthVibration = + when (this) { + CompassNorthVibration.NONE -> COMPASS_NORTH_VIBRATION_NONE + CompassNorthVibration.SHORT -> COMPASS_NORTH_VIBRATION_SHORT + CompassNorthVibration.MEDIUM -> COMPASS_NORTH_VIBRATION_MEDIUM + CompassNorthVibration.LONG -> COMPASS_NORTH_VIBRATION_LONG + } + + private fun CompassNorthVibrationProto.CompassNorthVibration.toData(): CompassNorthVibration = + when (this) { + COMPASS_NORTH_VIBRATION_SHORT -> + CompassNorthVibration.SHORT + COMPASS_NORTH_VIBRATION_MEDIUM, + COMPASS_NORTH_VIBRATION_UNSPECIFIED, + CompassNorthVibrationProto.CompassNorthVibration.UNRECOGNIZED -> + CompassNorthVibration.MEDIUM + COMPASS_NORTH_VIBRATION_LONG -> + CompassNorthVibration.LONG + COMPASS_NORTH_VIBRATION_NONE -> + CompassNorthVibration.NONE + } + private fun CoordinatesFormat.toProto(): CoordinatesFormatProto.CoordinatesFormat = when (this) { CoordinatesFormat.DD -> COORDINATES_FORMAT_DECIMAL_DEGREES diff --git a/data/src/main/java/io/trewartha/positional/data/settings/SettingsRepository.kt b/data/src/main/java/io/trewartha/positional/data/settings/SettingsRepository.kt index 81774f9c..688973ca 100644 --- a/data/src/main/java/io/trewartha/positional/data/settings/SettingsRepository.kt +++ b/data/src/main/java/io/trewartha/positional/data/settings/SettingsRepository.kt @@ -3,6 +3,7 @@ package io.trewartha.positional.data.settings import io.trewartha.positional.data.compass.CompassMode import io.trewartha.positional.data.location.CoordinatesFormat import io.trewartha.positional.data.measurement.Units +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.data.ui.LocationAccuracyVisibility import io.trewartha.positional.data.ui.Theme import kotlinx.coroutines.flow.Flow @@ -17,6 +18,11 @@ interface SettingsRepository { */ val compassMode: Flow + /** + * Style of device vibration to trigger when the compass crosses north + */ + val compassNorthVibration: Flow + /** * Coordinates format to display location coordinates in */ @@ -44,6 +50,13 @@ interface SettingsRepository { */ suspend fun setCompassMode(compassMode: CompassMode) + /** + * Sets the style of device vibration to trigger when the compass crosses north + * + * @param compassNorthVibration Style of device vibration to use + */ + suspend fun setCompassNorthVibration(compassNorthVibration: CompassNorthVibration) + /** * Sets the coordinates format to display location coordinates in * diff --git a/data/src/main/java/io/trewartha/positional/data/ui/CompassNorthVibration.kt b/data/src/main/java/io/trewartha/positional/data/ui/CompassNorthVibration.kt new file mode 100644 index 00000000..2b150b9d --- /dev/null +++ b/data/src/main/java/io/trewartha/positional/data/ui/CompassNorthVibration.kt @@ -0,0 +1,43 @@ +package io.trewartha.positional.data.ui + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +/** + * Length of device vibration to trigger when the compass crosses north + */ +enum class CompassNorthVibration { + + /** + * No vibration + */ + NONE { + override val duration = Duration.ZERO + }, + + /** + * Short vibration + */ + SHORT { + override val duration = 10.milliseconds + }, + + /** + * Medium vibration + */ + MEDIUM { + override val duration = 50.milliseconds + }, + + /** + * Long vibration + */ + LONG { + override val duration = 100.milliseconds + }; + + /** + * Duration of the vibration + */ + abstract val duration: Duration +} diff --git a/data/src/main/proto/compass_north_vibration.proto b/data/src/main/proto/compass_north_vibration.proto new file mode 100644 index 00000000..24bd47ad --- /dev/null +++ b/data/src/main/proto/compass_north_vibration.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "io.trewartha.positional.data.settings"; +option java_outer_classname = "CompassNorthVibrationProto"; + +enum CompassNorthVibration { + COMPASS_NORTH_VIBRATION_UNSPECIFIED = 0; + COMPASS_NORTH_VIBRATION_NONE = 1; + COMPASS_NORTH_VIBRATION_SHORT = 2; + COMPASS_NORTH_VIBRATION_MEDIUM = 3; + COMPASS_NORTH_VIBRATION_LONG = 4; +} diff --git a/data/src/main/proto/settings.proto b/data/src/main/proto/settings.proto index 2314e4a6..9c7c1351 100644 --- a/data/src/main/proto/settings.proto +++ b/data/src/main/proto/settings.proto @@ -1,6 +1,7 @@ syntax = "proto3"; import "compass_mode.proto"; +import "compass_north_vibration.proto"; import "coordinates_format.proto"; import "location_accuracy_visibility.proto"; import "theme.proto"; @@ -11,6 +12,7 @@ option java_outer_classname = "SettingsProto"; message Settings { CompassMode compass_mode = 1; + CompassNorthVibration compass_north_vibration = 6; CoordinatesFormat coordinates_format = 2; LocationAccuracyVisibility location_accuracy_visibility = 3; Theme theme = 4 ; diff --git a/data/src/test/java/io/trewartha/positional/data/settings/DataStoreSettingsRepositoryTest.kt b/data/src/test/java/io/trewartha/positional/data/settings/DataStoreSettingsRepositoryTest.kt index 44973315..cab50be7 100644 --- a/data/src/test/java/io/trewartha/positional/data/settings/DataStoreSettingsRepositoryTest.kt +++ b/data/src/test/java/io/trewartha/positional/data/settings/DataStoreSettingsRepositoryTest.kt @@ -6,6 +6,7 @@ import io.kotest.matchers.shouldBe import io.trewartha.positional.data.compass.CompassMode import io.trewartha.positional.data.location.CoordinatesFormat import io.trewartha.positional.data.measurement.Units +import io.trewartha.positional.data.ui.CompassNorthVibration import io.trewartha.positional.data.ui.LocationAccuracyVisibility import io.trewartha.positional.data.ui.Theme import kotlinx.coroutines.test.runTest @@ -125,4 +126,29 @@ class DataStoreSettingsRepositoryTest { awaitItem().shouldBe(Units.IMPERIAL) } } + + @Test + fun testCompassNorthVibrationDefaultAndSetAndGet() = runTest { + subject.compassNorthVibration.test { + withClue("Default compass north vibration should be medium") { + awaitItem().shouldBe(CompassNorthVibration.MEDIUM) + } + + subject.setCompassNorthVibration(CompassNorthVibration.NONE) + + awaitItem().shouldBe(CompassNorthVibration.NONE) + + subject.setCompassNorthVibration(CompassNorthVibration.SHORT) + + awaitItem().shouldBe(CompassNorthVibration.SHORT) + + subject.setCompassNorthVibration(CompassNorthVibration.MEDIUM) + + awaitItem().shouldBe(CompassNorthVibration.MEDIUM) + + subject.setCompassNorthVibration(CompassNorthVibration.LONG) + + awaitItem().shouldBe(CompassNorthVibration.LONG) + } + } }