diff --git a/README.md b/README.md index 4c6e9fe..36305d6 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,11 @@ Proximity | ✅️️ | rememberProximitySensorState() Gravity | ✅️ | rememberGravitySensorState() Linear Acceleration | ✅️ | rememberLinearAccelerationSensorState() Rotation Vector | ✅️️ | rememberRotationVectorSensorState() -Relative Humidity | ⚠️ | WIP +Relative Humidity | ✅️️ | rememberRelativeHumiditySensorState() Ambient Temperature | ✅️ | rememberAmbientTemperatureSensorState() Magnetic Field (Uncalibrated) | ✅️️ | rememberUncalibratedMagneticFieldSensorState() GameRotation Vector | ✅️ | rememberGameRotationVectorSensorState() -Gyroscope (Uncalibrated) | ⚠️ | WIP +Gyroscope (Uncalibrated) | ✅️ | rememberUncalibratedGyroscopeSensorState() Significant Motion | — | N/A Step Detector | ✅️ | rememberStepDetectorSensorState() Step Counter | ✅️ | rememberStepCounterSensorState() @@ -100,15 +100,15 @@ Pose6DOF | — | N/A Stationary Detect | ⚠️ | WIP Motion Detect | ⚠️ | WIP Heart Beat | — | N/A -Low Latency Off-Body Detect | — | N/A -Accelerometer (Uncalibrated) | ⚠️ | WIP +Low Latency Off-Body Detect | ✅️ | rememberLowLatencyOffBodyDetectSensorState() +Accelerometer (Uncalibrated) | ✅️ | rememberUncalibratedAccelerometerSensorState() Hinge Angle | ✅️ | rememberHingeAngleSensorState() -Head Tracker | — | N/A -Accelerometer Limited Axes | ⚠️ | WIP +Head Tracker | ✅️ | rememberHeadTrackerSensorState() +Accelerometer Limited Axes | ✅️ | rememberLimitedAxesAccelerometerSensorState() Gyroscope Limited Axes | ✅️️ | rememberLimitedAxesGyroscopeSensorState() -Accelerometer Limited Axes (Uncalibrated) | ⚠️ | WIP +Accelerometer Limited Axes (Uncalibrated) | ✅ | rememberUncalibratedLimitedAxesAccelerometerSensorState() Gyroscope Limited Axes (Uncalibrated) | — | N/A -Heading | ⚠️ | WIP +Heading | ✅ | rememberHeadingSensorState() All | — | N/A ## License 🔖 diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/HeadTrackerSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/HeadTrackerSensorState.kt new file mode 100644 index 0000000..507caef --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/HeadTrackerSensorState.kt @@ -0,0 +1,106 @@ +package com.mutualmobile.composesensors + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * The HeadTracker sensor measures the orientation of a user's head relative to an arbitrary reference frame, as well as the rate of rotation. + * @param xRotation X component of Euler vector representing rotation. Defaults to 0f. + * @param yRotation Y component of Euler vector representing rotation. Defaults to 0f. + * @param zRotation Z component of Euler vector representing rotation. Defaults to 0f. + * @param xAngularVelocity X component of Euler vector representing angular velocity (if supported, otherwise 0). Defaults to 0f. + * @param yAngularVelocity Y component of Euler vector representing angular velocity (if supported, otherwise 0). Defaults to 0f. + * @param zAngularVelocity Z component of Euler vector representing angular velocity (if supported, otherwise 0). Defaults to 0f. + * @param isAvailable Whether the current device has a gyroscope sensor. Defaults to false. + * @param accuracy Accuracy factor of the gyroscope sensor. Defaults to 0. + **/ +@Immutable +class HeadTrackerSensorState internal constructor( + val xRotation: Float = 0f, + val yRotation: Float = 0f, + val zRotation: Float = 0f, + val xAngularVelocity: Float = 0f, + val yAngularVelocity: Float = 0f, + val zAngularVelocity: Float = 0f, + val isAvailable: Boolean = false, + val accuracy: Int = 0 +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as HeadTrackerSensorState + + if (xRotation != other.xRotation) return false + if (yRotation != other.yRotation) return false + if (zRotation != other.zRotation) return false + if (xAngularVelocity != other.xAngularVelocity) return false + if (yAngularVelocity != other.yAngularVelocity) return false + if (zAngularVelocity != other.zAngularVelocity) return false + if (isAvailable != other.isAvailable) return false + if (accuracy != other.accuracy) return false + + return true + } + + override fun hashCode(): Int { + var result = xRotation.hashCode() + result = 31 * result + yRotation.hashCode() + result = 31 * result + zRotation.hashCode() + result = 31 * result + xAngularVelocity.hashCode() + result = 31 * result + yAngularVelocity.hashCode() + result = 31 * result + zAngularVelocity.hashCode() + result = 31 * result + isAvailable.hashCode() + result = 31 * result + accuracy + return result + } + + override fun toString(): String { + return "HeadTrackerSensorState(xRotation=$xRotation, yRotation=$yRotation, zRotation=$zRotation, xAngularVelocity=$xAngularVelocity, yAngularVelocity=$yAngularVelocity, zAngularVelocity=$zAngularVelocity, isAvailable=$isAvailable, accuracy=$accuracy)" + } +} + +/** + * Creates and [remember]s an instance of [HeadTrackerSensorState]. + * @param sensorDelay The rate at which the raw sensor data should be received. + * Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +@Composable +fun rememberHeadTrackerSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): HeadTrackerSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.HeadTracker, + sensorDelay = sensorDelay, + onError = onError + ) + + val headTrackerSensor = remember { mutableStateOf(HeadTrackerSensorState()) } + + LaunchedEffect(key1 = sensorState, block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + headTrackerSensor.value = HeadTrackerSensorState( + xRotation = sensorStateValues[0], + yRotation = sensorStateValues[1], + zRotation = sensorStateValues[2], + xAngularVelocity = sensorStateValues[3], + yAngularVelocity = sensorStateValues[4], + zAngularVelocity = sensorStateValues[5], + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy + ) + } + }) + + return headTrackerSensor.value +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/HeadingSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/HeadingSensorState.kt new file mode 100644 index 0000000..bbebb33 --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/HeadingSensorState.kt @@ -0,0 +1,81 @@ +package com.mutualmobile.composesensors + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * Measures a device's heading value in degrees (anti-clockwise), + * + * @param degrees Indicates direction in which the device is pointing + * relative to true north in degrees. Defaults to 0f. + * @param accuracy Indicates the confidence of prediction, under Gaussian + * standard normal distribution. Defaults to 0f. + * @param isAvailable Whether the current device has a heading sensor. + * Defaults to false. + */ +@Immutable +class HeadingSensorState internal constructor( + val degrees: Float = 0f, + val accuracy: Float = 0f, + val isAvailable: Boolean = false +) { + override fun hashCode(): Int { + var result = degrees.hashCode() + result = 31 * result + accuracy.hashCode() + result = 31 * result + isAvailable.hashCode() + return result + } + + override fun toString(): String { + return "HeadingSensorState(degrees=$degrees, accuracy=$accuracy, " + + "isAvailable=$isAvailable)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is HeadingSensorState) return false + + if (degrees != other.degrees) return false + if (accuracy != other.accuracy) return false + if (isAvailable != other.isAvailable) return false + + return true + } +} + +/** + * Creates and [remember]s an instance of [HeadingSensorState]. + * + * @param sensorDelay The rate at which the raw sensor data should be + * received. Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@Composable +fun rememberHeadingSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): HeadingSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.Heading, + sensorDelay = sensorDelay, + onError = onError + ) + + val headingSensorState = remember { mutableStateOf(HeadingSensorState()) } + + LaunchedEffect(key1 = sensorState, block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + headingSensorState.value = HeadingSensorState( + degrees = sensorStateValues[0], + accuracy = sensorStateValues[1], + isAvailable = sensorState.isAvailable + ) + } + }) + + return headingSensorState.value +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/HingeAngleSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/HingeAngleSensorState.kt index e4b2bfc..b3c2882 100644 --- a/composesensors/src/main/java/com/mutualmobile/composesensors/HingeAngleSensorState.kt +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/HingeAngleSensorState.kt @@ -1,5 +1,7 @@ package com.mutualmobile.composesensors +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect @@ -50,6 +52,7 @@ class HingeAngleSensorState internal constructor( * Defaults to [SensorDelay.Normal]. * @param onError Callback invoked on every error state. */ +@RequiresApi(Build.VERSION_CODES.R) @Composable fun rememberHingeAngleSensorState( sensorDelay: SensorDelay = SensorDelay.Normal, diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesAccelerometerSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesAccelerometerSensorState.kt new file mode 100644 index 0000000..0909840 --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesAccelerometerSensorState.kt @@ -0,0 +1,106 @@ +package com.mutualmobile.composesensors + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * An acceleration sensor determines the acceleration that is applied to a device by measuring the + * forces that are applied to the sensor itself. + * @param xForce Acceleration minus Gx on the x-axis (if supported) + * @param yForce Acceleration minus Gy on the y-axis (if supported) + * @param zForce Acceleration minus Gz on the z-axis (if supported) + * @param xAxisSupported Acceleration supported for x-axis. Defaults to false. + * @param yAxisSupported Acceleration supported for y-axis. Defaults to false. + * @param zAxisSupported Acceleration supported for z-axis. Defaults to false. + * @param isAvailable Whether the current device has an accelerometer with limited axes sensor. Defaults to false. + * @param accuracy Accuracy factor of the accelerometer with limited axes sensor. Defaults to 0. + */ +@Immutable +class LimitedAxesAccelerometerSensorState internal constructor( + val xForce: Float = 0f, + val yForce: Float = 0f, + val zForce: Float = 0f, + val xAxisSupported: Boolean = false, + val yAxisSupported: Boolean = false, + val zAxisSupported: Boolean = false, + val isAvailable: Boolean = false, + val accuracy: Int = 0 +) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is LimitedAxesAccelerometerSensorState) return false + + if (xForce != other.xForce) return false + if (yForce != other.yForce) return false + if (zForce != other.zForce) return false + if (xAxisSupported != other.xAxisSupported) return false + if (yAxisSupported != other.yAxisSupported) return false + if (zAxisSupported != other.zAxisSupported) return false + if (isAvailable != other.isAvailable) return false + if (accuracy != other.accuracy) return false + + return true + } + + override fun hashCode(): Int { + var result = xForce.hashCode() + result = 31 * result + yForce.hashCode() + result = 31 * result + zForce.hashCode() + result = 31 * result + xAxisSupported.hashCode() + result = 31 * result + yAxisSupported.hashCode() + result = 31 * result + zAxisSupported.hashCode() + result = 31 * result + isAvailable.hashCode() + result = 31 * result + accuracy + return result + } + + override fun toString(): String { + return "LimitedAxesAccelerometerSensorState(xForce=$xForce, yForce=$yForce, " + + "zForce=$zForce, xAxisSupported=$xAxisSupported, yAxisSupported=$yAxisSupported, " + + "zAxisSupported=$zAxisSupported, isAvailable=$isAvailable, accuracy=$accuracy)" + } +} + +/** + * Creates and [remember]s an instance of [LimitedAxesAccelerometerSensorState]. + * @param sensorDelay The rate at which the raw sensor data should be received. + * Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@Composable +fun rememberLimitedAxesAccelerometerSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): LimitedAxesAccelerometerSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.AccelerometerLimitedAxes, + sensorDelay = sensorDelay, + onError = onError + ) + val accelerometerSensorState = remember { mutableStateOf(LimitedAxesAccelerometerSensorState()) } + + LaunchedEffect( + key1 = sensorState, + block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + accelerometerSensorState.value = LimitedAxesAccelerometerSensorState( + xForce = sensorStateValues[0], + yForce = sensorStateValues[1], + zForce = sensorStateValues[2], + xAxisSupported = sensorStateValues[3] != 0f, + yAxisSupported = sensorStateValues[4] != 0f, + zAxisSupported = sensorStateValues[5] != 0f, + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy + ) + } + } + ) + + return accelerometerSensorState.value +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesGyroscopeSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesGyroscopeSensorState.kt index edc2e6c..ed9a334 100644 --- a/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesGyroscopeSensorState.kt +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/LimitedAxesGyroscopeSensorState.kt @@ -1,5 +1,7 @@ package com.mutualmobile.composesensors +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect @@ -11,9 +13,9 @@ import androidx.compose.runtime.remember * @param xRotation Angular speed around the x-axis (if supported). Defaults to 0f. * @param yRotation Angular speed around the y-axis (if supported). Defaults to 0f. * @param zRotation Angular speed around the z-axis (if supported). Defaults to 0f. - * @param xAxisSupported Angular speed supported for x-axis. Defaults to 0f. - * @param yAxisSupported Angular speed supported for y-axis. Defaults to 0f. - * @param zAxisSupported Angular speed supported for z-axis. Defaults to 0f. + * @param xAxisSupported Whether angular speed is supported for x-axis. Defaults to false. + * @param yAxisSupported Whether angular speed is supported for y-axis. Defaults to false. + * @param zAxisSupported Whether angular speed is supported for z-axis. Defaults to false. * @param isAvailable Whether the current device has a gyroscope sensor. Defaults to false. * @param accuracy Accuracy factor of the gyroscope sensor. Defaults to 0. */ @@ -22,9 +24,9 @@ class LimitedAxesGyroscopeSensorState internal constructor( val xRotation: Float = 0f, val yRotation: Float = 0f, val zRotation: Float = 0f, - val xAxisSupported: Float = 0f, - val yAxisSupported: Float = 0f, - val zAxisSupported: Float = 0f, + val xAxisSupported: Boolean = false, + val yAxisSupported: Boolean = false, + val zAxisSupported: Boolean = false, val isAvailable: Boolean = false, val accuracy: Int = 0 ) { @@ -73,7 +75,7 @@ class LimitedAxesGyroscopeSensorState internal constructor( * Defaults to [SensorDelay.Normal]. * @param onError Callback invoked on every error state. */ - +@RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable fun rememberLimitedAxesGyroscopeSensorState( sensorDelay: SensorDelay = SensorDelay.Normal, @@ -94,9 +96,9 @@ fun rememberLimitedAxesGyroscopeSensorState( xRotation = sensorStateValues[0], yRotation = sensorStateValues[1], zRotation = sensorStateValues[2], - xAxisSupported = sensorStateValues[3], - yAxisSupported = sensorStateValues[4], - zAxisSupported = sensorStateValues[5], + xAxisSupported = sensorStateValues[3] != 0f, + yAxisSupported = sensorStateValues[4] != 0f, + zAxisSupported = sensorStateValues[5] != 0f, isAvailable = sensorState.isAvailable, accuracy = sensorState.accuracy ) diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/LinearAccelerationSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/LinearAccelerationSensorState.kt index 03c0f7f..dfb90fb 100644 --- a/composesensors/src/main/java/com/mutualmobile/composesensors/LinearAccelerationSensorState.kt +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/LinearAccelerationSensorState.kt @@ -1,5 +1,7 @@ package com.mutualmobile.composesensors +import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect @@ -21,7 +23,7 @@ class LinearAccelerationSensorState internal constructor( val yForce: Float = 0f, val zForce: Float = 0f, val isAvailable: Boolean = false, - val accuracy: Int = 0, + val accuracy: Int = 0 ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -47,7 +49,7 @@ class LinearAccelerationSensorState internal constructor( override fun toString(): String { return "LinearAccelerationSensorState(xForce=$xForce, yForce=$yForce, zForce=$zForce, " + - "isAvailable=$isAvailable, accuracy=$accuracy)" + "isAvailable=$isAvailable, accuracy=$accuracy)" } } @@ -57,15 +59,16 @@ class LinearAccelerationSensorState internal constructor( * Defaults to [SensorDelay.Normal]. * @param onError Callback invoked on every error state. */ +@RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable fun rememberLinearAccelerationSensorState( sensorDelay: SensorDelay = SensorDelay.Normal, - onError: (throwable: Throwable) -> Unit = {}, + onError: (throwable: Throwable) -> Unit = {} ): LinearAccelerationSensorState { val sensorState = rememberSensorState( sensorType = SensorType.LinearAcceleration, sensorDelay = sensorDelay, - onError = onError, + onError = onError ) val linearAccelerationSensorState = remember { mutableStateOf(LinearAccelerationSensorState()) } diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/LowLatencyOffBodyDetectSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/LowLatencyOffBodyDetectSensorState.kt new file mode 100644 index 0000000..bd05424 --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/LowLatencyOffBodyDetectSensorState.kt @@ -0,0 +1,83 @@ +package com.mutualmobile.composesensors + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * A low latency off body detect sensor that returns an event every time the device transitions from off-body to on-body and from on-body to off-body (e.g. a wearable device being removed from the wrist would trigger an event indicating an off-body transition). + * This sensor deliver the initial on-body or off-body event representing the current device state within 5 seconds of activating the sensor. + * This sensor must be able to detect and report an on-body to off-body transition within 1 second of the device being removed from the body, and must be able to detect and report an off-body to on-body transition within 5 seconds of the device being put back onto the body. + * @param isDeviceOnBody This sensor produces only two values true and false where true means currently device is in on-body state and false means device is in off-body state and Defaults set to false. + * @param isAvailable Whether the current device has an low latency off body detect sensor. Defaults to false. + * @param accuracy Accuracy factor of the low latency off body detect sensor. Defaults to 0. + */ +@Immutable +class LowLatencyOffBodyDetectSensorState internal constructor( + val isDeviceOnBody: Boolean = false, + val isAvailable: Boolean = false, + val accuracy: Int = 0 +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is LowLatencyOffBodyDetectSensorState) return false + + if (isDeviceOnBody != other.isDeviceOnBody) return false + if (isAvailable != other.isAvailable) return false + if (accuracy != other.accuracy) return false + + return true + } + + override fun hashCode(): Int { + var result = isDeviceOnBody.hashCode() + result = 31 * result + isAvailable.hashCode() + result = 31 * result + accuracy.hashCode() + return result + } + + override fun toString(): String { + return "LowLatencyOffBodyDetectSensorState(isDeviceOnBody=$isDeviceOnBody, isAvailable=$isAvailable, accuracy=$accuracy)" + } +} + +/** + * Creates and [remember]s an instance of [LowLatencyOffBodyDetectSensorState]. + * @param sensorDelay The rate at which the raw sensor data should be received. + * Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun rememberLowLatencyOffBodyDetectSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): LowLatencyOffBodyDetectSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.LowLatencyOffBodyDetect, + sensorDelay = sensorDelay, + onError = onError + ) + val lowLatencyOffBodyDetectSensorState = + remember { mutableStateOf(LowLatencyOffBodyDetectSensorState()) } + + LaunchedEffect( + key1 = sensorState, + block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + lowLatencyOffBodyDetectSensorState.value = LowLatencyOffBodyDetectSensorState( + isDeviceOnBody = sensorStateValues[0].toInt() == 1, + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy + ) + } + } + ) + + return lowLatencyOffBodyDetectSensorState.value +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/PressureSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/PressureSensorState.kt index 978d5fc..598cfb2 100644 --- a/composesensors/src/main/java/com/mutualmobile/composesensors/PressureSensorState.kt +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/PressureSensorState.kt @@ -16,7 +16,7 @@ import androidx.compose.runtime.remember class PressureSensorState internal constructor( val pressure: Float = 0f, val isAvailable: Boolean = false, - val accuracy: Int = 0, + val accuracy: Int = 0 ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -37,7 +37,7 @@ class PressureSensorState internal constructor( } override fun toString(): String { - return "PressureState(pressure=$pressure, isAvailable=$isAvailable, accuracy=$accuracy)" + return "PressureSensorState(pressure=$pressure, isAvailable=$isAvailable, accuracy=$accuracy)" } } @@ -48,14 +48,14 @@ class PressureSensorState internal constructor( * @param onError Callback invoked on every error state. */ @Composable -fun rememberPressureState( +fun rememberPressureSensorState( sensorDelay: SensorDelay = SensorDelay.Normal, - onError: (throwable: Throwable) -> Unit = {}, + onError: (throwable: Throwable) -> Unit = {} ): PressureSensorState { val sensorState = rememberSensorState( sensorType = SensorType.Pressure, sensorDelay = sensorDelay, - onError = onError, + onError = onError ) val pressureSensorState = remember { mutableStateOf(PressureSensorState()) } diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/RelativeHumiditySensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/RelativeHumiditySensorState.kt new file mode 100644 index 0000000..4eb2631 --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/RelativeHumiditySensorState.kt @@ -0,0 +1,105 @@ +package com.mutualmobile.composesensors + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import kotlin.math.exp +import kotlin.math.ln + +/** + * Measures the Relative humidity value. + * + * Dew Point is the temperature to which a given parcel of air must be cooled. The unit is Centigrade. + * Absolute humidity is the mass of water vapor in a particular volume of dry air. The unit is g/m3 + * [Relative Humidity](https://developer.android.com/reference/android/hardware/SensorEvent#sensor.type_relative_humidity:) + * + * @param relativeHumidity Level of relative humidity in the air (in percentage). + * @param isAvailable Whether the current device has a relative humidity sensor. Defaults to false. + * @param actualTemp Actual current temperature as measured by the device. Defaults to 0. + * @param accuracy Accuracy factor of the relative humidity sensor. Defaults to 0. + */ +@Immutable +class RelativeHumiditySensorState internal constructor( + val relativeHumidity: Float = 0f, + val isAvailable: Boolean = false, + val actualTemp: Float = 0f, + val accuracy: Int = 0 +) { + val absoluteHumidity = 216.7 * + ( + relativeHumidity / 100.0 * 6.112 * exp( + 17.62 * actualTemp / + (243.12 + actualTemp) + ) / (273.15 + actualTemp) + ) + + private val humidity = ln(relativeHumidity / 100.0) + + (17.62 * actualTemp) / (243.12 + actualTemp) + + val dewPointTemperature = 243.12 * (humidity / (17.62 - humidity)) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RelativeHumiditySensorState) return false + + if (relativeHumidity != other.relativeHumidity) return false + if (isAvailable != other.isAvailable) return false + if (accuracy != other.accuracy) return false + + return true + } + + override fun hashCode(): Int { + var result = relativeHumidity.hashCode() + result = 31 * result + isAvailable.hashCode() + result = 31 * result + accuracy.hashCode() + return result + } + + override fun toString(): String { + return "HumiditySensorState(relativeHumidity=$relativeHumidity, " + + "isAvailable=$isAvailable, " + + "accuracy=$accuracy)" + } + + /** + * Creates and [remember]s an instance of [RelativeHumiditySensorState]. + * @param sensorDelay The rate at which the raw sensor data should be received. + * Defaults to [SensorDelay.Normal]. + * @param actualTemp is passed into the function only if User requires calculations on Dew Point and absolute Humidity + * This can be accessed using the dot operator. (e.g -> rememberRelativeHumiditySensorState().absoluteHumidity) + * @param onError Callback invoked on every error state. + */ + @Composable + fun rememberRelativeHumiditySensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + actualTemp: Float = 0F, + onError: (throwable: Throwable) -> Unit = {} + ): RelativeHumiditySensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.RelativeHumidity, + sensorDelay = sensorDelay, + onError = onError + ) + val relativeHumiditySensorState = remember { mutableStateOf(RelativeHumiditySensorState()) } + + LaunchedEffect( + key1 = sensorState, + block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + relativeHumiditySensorState.value = RelativeHumiditySensorState( + relativeHumidity = sensorStateValues[0], + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy, + actualTemp = actualTemp + ) + } + } + ) + + return relativeHumiditySensorState.value + } +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedAccelerometerSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedAccelerometerSensorState.kt new file mode 100644 index 0000000..a6bb276 --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedAccelerometerSensorState.kt @@ -0,0 +1,120 @@ +package com.mutualmobile.composesensors + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * An acceleration sensor determines the acceleration that is applied to a + * device by measuring the forces that are applied to the sensor itself. + * In uncalibrated sensor, there are extra biases as estimated in x, y, z + * direction, such that uncalibrated =_value = calibrated_value + bias in + * that particular direction + * + * @param xForce Acceleration force along the x axis (including gravity) in + * m/s^2. Defaults to 0f. + * @param yForce Acceleration force along the y axis (including gravity) in + * m/s^2. Defaults to 0f. + * @param zForce Acceleration force along the z axis (including gravity) in + * m/s^2. Defaults to 0f. + * @param xBias Estimated bias in the x direction in + * m/s^2. Defaults to 0f. + * @param yBias Estimated bias in the y direction in + * m/s^2. Defaults to 0f. + * @param zBias Estimated bias in the z direction in + * m/s^2. Defaults to 0f. + * @param isAvailable Whether the current device has an accelerometer + * sensor. Defaults to false. + * @param accuracy Accuracy factor of the accelerometer sensor. Defaults + * to 0. + */ +@Immutable +class UncalibratedAccelerometerSensorState internal constructor( + val xForce: Float = 0f, + val yForce: Float = 0f, + val zForce: Float = 0f, + val xBias: Float = 0f, + val yBias: Float = 0f, + val zBias: Float = 0f, + val isAvailable: Boolean = false, + val accuracy: Int = 0 +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UncalibratedAccelerometerSensorState) return false + + if (xForce != other.xForce) return false + if (yForce != other.yForce) return false + if (zForce != other.zForce) return false + if (xBias != other.xBias) return false + if (yBias != other.yBias) return false + if (zBias != other.zBias) return false + if (isAvailable != other.isAvailable) return false + if (accuracy != other.accuracy) return false + + return true + } + + override fun hashCode(): Int { + var result = xForce.hashCode() + result = 31 * result + yForce.hashCode() + result = 31 * result + zForce.hashCode() + result = 31 * result + xBias.hashCode() + result = 31 * result + yBias.hashCode() + result = 31 * result + zBias.hashCode() + result = 31 * result + isAvailable.hashCode() + result = 31 * result + accuracy.hashCode() + return result + } + + override fun toString(): String { + return "AccelerometerSensorState(xForce=$xForce, yForce=$yForce, " + + "zForce=$zForce, xBiased=$xBias, yBiased=$yBias, zBiased=$zBias," + + "isAvailable=$isAvailable, accuracy=$accuracy)" + } +} + +/** + * Creates and [remember]s an instance of + * [UncalibratedAccelerometerSensorState]. + * + * @param sensorDelay The rate at which the raw sensor data should be + * received. Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@Composable +fun rememberUncalibratedAccelerometerSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): UncalibratedAccelerometerSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.AccelerometerUncalibrated, + sensorDelay = sensorDelay, + onError = onError + ) + val uncalibratedAccelerometerSensorState = + remember { mutableStateOf(UncalibratedAccelerometerSensorState()) } + + LaunchedEffect( + key1 = sensorState, + block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + uncalibratedAccelerometerSensorState.value = UncalibratedAccelerometerSensorState( + xForce = sensorStateValues[0], + yForce = sensorStateValues[1], + zForce = sensorStateValues[2], + xBias = sensorStateValues[3], + yBias = sensorStateValues[4], + zBias = sensorStateValues[5], + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy + ) + } + } + ) + + return uncalibratedAccelerometerSensorState.value +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedGyroscopeSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedGyroscopeSensorState.kt new file mode 100644 index 0000000..81f59de --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedGyroscopeSensorState.kt @@ -0,0 +1,109 @@ +package com.mutualmobile.composesensors + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * Measures a device's rate of rotation in rad/s around each of the three + * physical axes (x, y, and z). Uncalibrated gyroscope sensor estimates an + * extra drift on x, y, z axes, to the effect that uncalibrated_value = + * calibrated_value + drift in that particular axis. + * + * @param xRotation Rate of rotation around the x axis. Defaults to 0f. + * @param yRotation Rate of rotation around the y axis. Defaults to 0f. + * @param zRotation Rate of rotation around the z axis. Defaults to 0f. + * @param xBias Bias around the x axis. Defaults to 0f. + * @param yBias Bias around the y axis. Defaults to 0f. + * @param zBias Bias around the z axis. Defaults to 0f. + * @param isAvailable Whether the current device has a gyroscope sensor. + * Defaults to false. + * @param accuracy Accuracy factor of the gyroscope sensor. Defaults to 0. + */ +@Immutable +class UncalibratedGyroscopeSensorState internal constructor( + val xRotation: Float = 0f, + val yRotation: Float = 0f, + val zRotation: Float = 0f, + val xBias: Float = 0f, + val yBias: Float = 0f, + val zBias: Float = 0f, + val isAvailable: Boolean = false, + val accuracy: Int = 0 +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UncalibratedGyroscopeSensorState) return false + + if (xRotation != other.xRotation) return false + if (yRotation != other.yRotation) return false + if (zRotation != other.zRotation) return false + if (xBias != other.xBias) return false + if (yBias != other.yBias) return false + if (zBias != other.zBias) return false + if (isAvailable != other.isAvailable) return false + + return true + } + + override fun hashCode(): Int { + var result = xRotation.hashCode() + result = 31 * result + yRotation.hashCode() + result = 31 * result + zRotation.hashCode() + result = 31 * result + xBias.hashCode() + result = 31 * result + yBias.hashCode() + result = 31 * result + zBias.hashCode() + result = 31 * result + isAvailable.hashCode() + return result + } + + override fun toString(): String { + return "GyroscopeSensorState(xRotation=$xRotation, yRotation=$yRotation, " + + "zRotation=$zRotation," + "xBias=$xBias," + "yBias=$yBias," + "zBias=$zBias," + + " isAvailable=$isAvailable, accuracy=$accuracy)" + } +} + +/** + * Creates and [remember]s an instance of [GyroscopeSensorState]. + * + * @param sensorDelay The rate at which the raw sensor data should be + * received. Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@Composable +fun rememberUncalibratedGyroscopeSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): UncalibratedGyroscopeSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.GyroscopeUncalibrated, + sensorDelay = sensorDelay, + onError = onError + ) + val uncalibratedGyroscopeSensorState = + remember { mutableStateOf(UncalibratedGyroscopeSensorState()) } + + LaunchedEffect( + key1 = sensorState, + block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + uncalibratedGyroscopeSensorState.value = UncalibratedGyroscopeSensorState( + xRotation = sensorStateValues[0], + yRotation = sensorStateValues[1], + zRotation = sensorStateValues[2], + xBias = sensorStateValues[3], + yBias = sensorStateValues[4], + zBias = sensorStateValues[5], + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy + ) + } + } + ) + + return uncalibratedGyroscopeSensorState.value +} diff --git a/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedLimitedAxesAccelerometerSensorState.kt b/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedLimitedAxesAccelerometerSensorState.kt new file mode 100644 index 0000000..f680709 --- /dev/null +++ b/composesensors/src/main/java/com/mutualmobile/composesensors/UncalibratedLimitedAxesAccelerometerSensorState.kt @@ -0,0 +1,121 @@ +package com.mutualmobile.composesensors + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember + +/** + * An acceleration sensor determines the acceleration that is applied to a device by measuring the + * forces that are applied to the sensor itself. + * @param xForce Uncalibrated X force without bias compensation (if supported). Defaults to 0f. + * @param yForce Uncalibrated Y force without bias compensation (if supported). Defaults to 0f. + * @param zForce Uncalibrated Z force without bias compensation (if supported). Defaults to 0f. + * @param xBias Estimated X bias (if supported). Defaults to 0f. + * @param yBias Estimated Y bias (if supported). Defaults to 0f. + * @param zBias Estimated Z bias (if supported). Defaults to 0f. + * @param xAxisSupported Acceleration supported for x-axis. Defaults to false. + * @param yAxisSupported Acceleration supported for y-axis. Defaults to false. + * @param zAxisSupported Acceleration supported for z-axis. Defaults to false. + * @param isAvailable Whether the current device has an accelerometer limited axes (uncalibrated) sensor. Defaults to false. + * @param accuracy Accuracy factor of the accelerometer limited axes (uncalibrated) sensor. Defaults to 0. + */ +@Immutable +class UncalibratedLimitedAxesAccelerometerSensorState internal constructor( + val xForce: Float = 0f, + val yForce: Float = 0f, + val zForce: Float = 0f, + val xBias: Float = 0f, + val yBias: Float = 0f, + val zBias: Float = 0f, + val xAxisSupported: Boolean = false, + val yAxisSupported: Boolean = false, + val zAxisSupported: Boolean = false, + val isAvailable: Boolean = false, + val accuracy: Int = 0 +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is UncalibratedLimitedAxesAccelerometerSensorState) return false + + if (xForce != other.xForce) return false + if (yForce != other.yForce) return false + if (zForce != other.zForce) return false + if (xBias != other.xBias) return false + if (yBias != other.yBias) return false + if (zBias != other.zBias) return false + if (xAxisSupported != other.xAxisSupported) return false + if (yAxisSupported != other.yAxisSupported) return false + if (zAxisSupported != other.zAxisSupported) return false + if (isAvailable != other.isAvailable) return false + if (accuracy != other.accuracy) return false + + return true + } + + override fun hashCode(): Int { + var result = xForce.hashCode() + result = 31 * result + yForce.hashCode() + result = 31 * result + zForce.hashCode() + result = 31 * result + xBias.hashCode() + result = 31 * result + yBias.hashCode() + result = 31 * result + zBias.hashCode() + result = 31 * result + xAxisSupported.hashCode() + result = 31 * result + yAxisSupported.hashCode() + result = 31 * result + zAxisSupported.hashCode() + result = 31 * result + isAvailable.hashCode() + result = 31 * result + accuracy + return result + } + + override fun toString(): String { + return "UncalibratedLimitedAxesAccelerometerSensorState(xForce=$xForce, yForce=$yForce, " + + "zForce=$zForce, xBias=$xBias, yBias=$yBias, zBias=$zBias, " + + "xAxisSupported=$xAxisSupported, yAxisSupported=$yAxisSupported, " + + "zAxisSupported=$zAxisSupported, isAvailable=$isAvailable, accuracy=$accuracy)" + } +} + +/** + * Creates and [remember]s an instance of [UncalibratedLimitedAxesAccelerometerSensorState]. + * @param sensorDelay The rate at which the raw sensor data should be received. + * Defaults to [SensorDelay.Normal]. + * @param onError Callback invoked on every error state. + */ +@Composable +fun rememberUncalibratedLimitedAxesAccelerometerSensorState( + sensorDelay: SensorDelay = SensorDelay.Normal, + onError: (throwable: Throwable) -> Unit = {} +): UncalibratedLimitedAxesAccelerometerSensorState { + val sensorState = rememberSensorState( + sensorType = SensorType.AccelerometerLimitedAxesUncalibrated, + sensorDelay = sensorDelay, + onError = onError + ) + val accelerometerSensorState = remember { mutableStateOf(UncalibratedLimitedAxesAccelerometerSensorState()) } + + LaunchedEffect( + key1 = sensorState, + block = { + val sensorStateValues = sensorState.data + if (sensorStateValues.isNotEmpty()) { + accelerometerSensorState.value = UncalibratedLimitedAxesAccelerometerSensorState( + xForce = sensorStateValues[0], + yForce = sensorStateValues[1], + zForce = sensorStateValues[2], + xBias = sensorStateValues[3], + yBias = sensorStateValues[4], + zBias = sensorStateValues[5], + xAxisSupported = sensorStateValues[6] != 0f, + yAxisSupported = sensorStateValues[7] != 0f, + zAxisSupported = sensorStateValues[8] != 0f, + isAvailable = sensorState.isAvailable, + accuracy = sensorState.accuracy + ) + } + } + ) + + return accelerometerSensorState.value +} diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 12b523d..1339e17 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -60,6 +60,7 @@ dependencies { val jUnitVersion = "4.13.2" val androidJUnitVersion = "1.1.5" val espressoVersion = "3.5.1" + val splashScreenVersion = "1.0.1" implementation(project(mapOf("path" to ":composesensors"))) implementation("androidx.core:core-ktx:$coreKtxVersion") @@ -75,4 +76,5 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion") debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion") debugImplementation("androidx.compose.ui:ui-test-manifest:$composeVersion") + implementation("androidx.core:core-splashscreen:$splashScreenVersion") } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 904852b..d6ab55b 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -10,12 +10,14 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.ComposeSensors" + android:theme="@style/Theme.App.Starting" tools:targetApi="31"> + android:screenOrientation="portrait" + android:theme="@style/Theme.App.Starting" + tools:ignore="LockedOrientationActivity"> diff --git a/sample/src/main/ic_launcher-playstore.png b/sample/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..1c1d8dc Binary files /dev/null and b/sample/src/main/ic_launcher-playstore.png differ diff --git a/sample/src/main/java/com/mutualmobile/sample/MainActivity.kt b/sample/src/main/java/com/mutualmobile/sample/MainActivity.kt index 1333fde..67a6e0a 100644 --- a/sample/src/main/java/com/mutualmobile/sample/MainActivity.kt +++ b/sample/src/main/java/com/mutualmobile/sample/MainActivity.kt @@ -6,16 +6,19 @@ import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.mutualmobile.composesensors.rememberAccelerometerSensorState +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.core.view.WindowCompat +import com.mutualmobile.sample.ui.screens.sensorlist.SensorsListScreen import com.mutualmobile.sample.ui.theme.ComposeSensorsTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() super.onCreate(savedInstanceState) + + WindowCompat.setDecorFitsSystemWindows(window, false) + setContent { ComposeSensorsTheme { // A surface container using the 'background' color from the theme @@ -23,32 +26,9 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - val accelerometerState = rememberAccelerometerSensorState() - Text( - text = "Force X: ${accelerometerState.xForce}" + - "\nForce Y: ${accelerometerState.yForce}" + - "\nForce Z: ${accelerometerState.zForce}" + - "\nIs Available?: ${accelerometerState.isAvailable}," + - "\nAccuracy?: ${accelerometerState.accuracy}" - ) + SensorsListScreen() } } } } } - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - ComposeSensorsTheme { - Greeting("Android") - } -} \ No newline at end of file diff --git a/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/SensorsListScreen.kt b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/SensorsListScreen.kt new file mode 100644 index 0000000..2fe55ad --- /dev/null +++ b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/SensorsListScreen.kt @@ -0,0 +1,330 @@ +package com.mutualmobile.sample.ui.screens.sensorlist + +import android.annotation.SuppressLint +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowLeft +import androidx.compose.material.icons.filled.KeyboardArrowRight +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +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.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.mutualmobile.composesensors.rememberAccelerometerSensorState +import com.mutualmobile.composesensors.rememberAmbientTemperatureSensorState +import com.mutualmobile.composesensors.rememberGameRotationVectorSensorState +import com.mutualmobile.composesensors.rememberGravitySensorState +import com.mutualmobile.composesensors.rememberGyroscopeSensorState +import com.mutualmobile.composesensors.rememberHeartRateSensorState +import com.mutualmobile.composesensors.rememberHingeAngleSensorState +import com.mutualmobile.composesensors.rememberLightSensorState +import com.mutualmobile.composesensors.rememberLimitedAxesGyroscopeSensorState +import com.mutualmobile.composesensors.rememberLinearAccelerationSensorState +import com.mutualmobile.composesensors.rememberLowLatencyOffBodyDetectSensorState +import com.mutualmobile.composesensors.rememberMagneticFieldSensorState +import com.mutualmobile.composesensors.rememberPressureSensorState +import com.mutualmobile.composesensors.rememberProximitySensorState +import com.mutualmobile.composesensors.rememberRotationVectorSensorState +import com.mutualmobile.composesensors.rememberUncalibratedMagneticFieldSensorState +import com.mutualmobile.sample.R +import com.mutualmobile.sample.ui.screens.sensorlist.components.CSButton +import com.mutualmobile.sample.ui.screens.sensorlist.components.CSButtonPosition +import com.mutualmobile.sample.ui.screens.sensorlist.components.SensorItem +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@Composable +fun SensorsListScreen() { + var isTopBarTitleCollapsed by remember { mutableStateOf(false) } + val pagerState = rememberPagerState() + val coroutineScope = rememberCoroutineScope() + + // TODO: Keep this updated until a better option is found + val totalPageCount = rememberSaveable { 16 } + val accelerometerState = rememberAccelerometerSensorState() + val magneticFieldState = rememberMagneticFieldSensorState() + val gyroscopeState = rememberGyroscopeSensorState() + val lightState = rememberLightSensorState() + val pressureState = rememberPressureSensorState() + val proximityState = rememberProximitySensorState() + val gravityState = rememberGravitySensorState() + val linearAccelerationState = rememberLinearAccelerationSensorState() + val rotationVectorState = rememberRotationVectorSensorState() + val ambientTemperatureState = rememberAmbientTemperatureSensorState() + val uncalibratedMagneticFieldState = rememberUncalibratedMagneticFieldSensorState() + val gameRotationVectorState = rememberGameRotationVectorSensorState() + val heartRateState = rememberHeartRateSensorState() + val lowLatencyOffBodyDetectState = rememberLowLatencyOffBodyDetectSensorState() + val hingeAngleState = rememberHingeAngleSensorState() + val limitedAxesGyroscopeState = rememberLimitedAxesGyroscopeSensorState() + + // Trigger TopBar animation once + LaunchedEffect(Unit) { + delay(2000) + isTopBarTitleCollapsed = true + delay(2000) + isTopBarTitleCollapsed = false + } + + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = { isTopBarTitleCollapsed = !isTopBarTitleCollapsed } + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(id = R.drawable.app_logo), + contentDescription = null + ) + } + AnimatedVisibility( + visible = !isTopBarTitleCollapsed + ) { + Text(text = "ComposeSensors") + } + } + } + ) + }, + bottomBar = { + Row( + modifier = Modifier + .navigationBarsPadding() + .padding(16.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CSButton( + text = "Previous", + icon = Icons.Default.KeyboardArrowLeft, + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage( + pagerState.currentPage - 1, + animationSpec = tween(durationMillis = 700) + ) + } + }, + onLongClick = { + coroutineScope.launch { + pagerState.scrollToPage(0) + } + }, + position = CSButtonPosition.Start, + enabled = pagerState.currentPage != 0 + ) + CSButton( + text = "Next", + icon = Icons.Default.KeyboardArrowRight, + onClick = { + coroutineScope.launch { + pagerState.animateScrollToPage( + pagerState.currentPage + 1, + animationSpec = tween(durationMillis = 700) + ) + } + }, + onLongClick = { + coroutineScope.launch { + pagerState.scrollToPage(totalPageCount) + } + }, + position = CSButtonPosition.End, + enabled = pagerState.currentPage != totalPageCount - 1 + ) + } + } + ) { + HorizontalPager( + pageCount = totalPageCount, + state = pagerState, + contentPadding = PaddingValues(32.dp), + beyondBoundsPageCount = 1, + userScrollEnabled = false + ) { index -> + val scrollProgress = remember { + derivedStateOf { + pagerState.currentOffsetForPage(index) + } + } + SensorItem( + name = when (index) { + 0 -> "Accelerometer" + 1 -> "Magnetic Field" + 2 -> "Gyroscope" + 3 -> "Light" + 4 -> "Pressure" + 5 -> "Proximity" + 6 -> "Gravity" + 7 -> "Linear Acceleration" + 8 -> "Rotation Vector" + 9 -> "Ambient Temperature" + 10 -> "Magnetic Field (Uncalibrated)" + 11 -> "Game Rotation Vector" + 12 -> "Heart Rate" + 13 -> "Low Latency Off-Body Detection" + 14 -> "Hinge Angle" + 15 -> "Gyroscope (Limited Axes)" + else -> error("Invalid index '$index'") + }, + scrollProgress = scrollProgress, + imageRef = when (index) { + 0 -> R.drawable.accelerometer + 1 -> R.drawable.magnetic_field + 2 -> R.drawable.gyroscope + 3 -> R.drawable.light + 4 -> R.drawable.pressure + 5 -> R.drawable.proximity + 6 -> R.drawable.gravity + 7 -> R.drawable.linear_acceleration + 8 -> R.drawable.rotation_vector + 9 -> R.drawable.ambient_temperature + 10 -> R.drawable.magnetic_field + 11 -> R.drawable.game_rotation_vector + 12 -> R.drawable.heart_rate + 13 -> R.drawable.low_latency_off_body_detect + 14 -> R.drawable.hinge_angle + 15 -> R.drawable.gyroscope + else -> error("Invalid index '$index'") + }, + sensorValues = when (index) { + 0 -> mapOf( + "Force X" to accelerometerState.xForce, + "Force Y" to accelerometerState.yForce, + "Force Z" to accelerometerState.zForce + ) + + 1 -> mapOf( + "Strength X" to magneticFieldState.xStrength, + "Strength Y" to magneticFieldState.yStrength, + "Strength Z" to magneticFieldState.zStrength + ) + + 2 -> mapOf( + "Rotation X" to gyroscopeState.xRotation, + "Rotation Y" to gyroscopeState.yRotation, + "Rotation Z" to gyroscopeState.zRotation + ) + + 3 -> mapOf("Illuminance" to lightState.illuminance) + + 4 -> mapOf("Pressure" to pressureState.pressure) + + 5 -> mapOf("Distance" to proximityState.sensorDistance) + + 6 -> mapOf( + "Force X" to gravityState.xForce, + "Force Y" to gravityState.yForce, + "Force Z" to gravityState.zForce + ) + + 7 -> mapOf( + "Force X" to linearAccelerationState.xForce, + "Force Y" to linearAccelerationState.yForce, + "Force Z" to linearAccelerationState.zForce + ) + + 8 -> mapOf( + "Vector X" to rotationVectorState.vectorX, + "Vector Y" to rotationVectorState.vectorY, + "Vector Z" to rotationVectorState.vectorZ, + "Scalar" to rotationVectorState.scalar, + "Heading Accuracy" to rotationVectorState.estimatedHeadingAccuracy + ) + + 9 -> mapOf("Temperature" to ambientTemperatureState.temperature) + + 10 -> mapOf( + "Strength X" to uncalibratedMagneticFieldState.xStrength, + "Strength Y" to uncalibratedMagneticFieldState.yStrength, + "Strength Z" to uncalibratedMagneticFieldState.zStrength, + "Bias X" to uncalibratedMagneticFieldState.xBias, + "Bias Y" to uncalibratedMagneticFieldState.yBias, + "Bias Z" to uncalibratedMagneticFieldState.zBias + ) + + 11 -> mapOf( + "Vector X" to gameRotationVectorState.vectorX, + "Vector Y" to gameRotationVectorState.vectorY, + "Vector Z" to gameRotationVectorState.vectorZ + ) + + 12 -> mapOf("Heart Rate" to heartRateState.heartRate) + + 13 -> mapOf("Is On Body?" to lowLatencyOffBodyDetectState.isDeviceOnBody) + + 14 -> mapOf("Angle" to hingeAngleState.angle) + + 15 -> mapOf( + "Rotation X" to limitedAxesGyroscopeState.xRotation, + "Rotation Y" to limitedAxesGyroscopeState.yRotation, + "Rotation Z" to limitedAxesGyroscopeState.zRotation, + "X Supported?" to limitedAxesGyroscopeState.xAxisSupported, + "Y Supported?" to limitedAxesGyroscopeState.yAxisSupported, + "Z Supported?" to limitedAxesGyroscopeState.zAxisSupported + ) + + else -> error("Invalid index '$index'") + }, + isAvailable = when (index) { + 0 -> accelerometerState.isAvailable + 1 -> magneticFieldState.isAvailable + 2 -> gyroscopeState.isAvailable + 3 -> lightState.isAvailable + 4 -> pressureState.isAvailable + 5 -> proximityState.isAvailable + 6 -> gravityState.isAvailable + 7 -> linearAccelerationState.isAvailable + 8 -> rotationVectorState.isAvailable + 9 -> ambientTemperatureState.isAvailable + 10 -> uncalibratedMagneticFieldState.isAvailable + 11 -> gameRotationVectorState.isAvailable + 12 -> heartRateState.isAvailable + 13 -> lowLatencyOffBodyDetectState.isAvailable + 14 -> hingeAngleState.isAvailable + 15 -> limitedAxesGyroscopeState.isAvailable + else -> error("Invalid index '$index'") + } + ) + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +fun PagerState.currentOffsetForPage(page: Int): Float { + return (currentPage - page) + currentPageOffsetFraction +} diff --git a/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/CSButton.kt b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/CSButton.kt new file mode 100644 index 0000000..da97289 --- /dev/null +++ b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/CSButton.kt @@ -0,0 +1,103 @@ +package com.mutualmobile.sample.ui.screens.sensorlist.components + +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp + +enum class CSButtonPosition { Start, End } + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun CSButton( + text: String, + icon: ImageVector, + onClick: () -> Unit, + onLongClick: () -> Unit, + position: CSButtonPosition, + enabled: Boolean, + modifier: Modifier = Modifier, + shape: Shape = MaterialTheme.shapes.small, + contentPadding: PaddingValues = when (position) { + CSButtonPosition.Start -> PaddingValues(start = 4.dp, end = 16.dp) + CSButtonPosition.End -> PaddingValues(start = 16.dp, end = 8.dp) + } +) { + Surface( + modifier = modifier + .graphicsLayer { + this.clip = true + this.shape = shape + } + .combinedClickable( + interactionSource = remember { MutableInteractionSource() }, + indication = if (enabled) { + rememberRipple(color = MaterialTheme.colorScheme.onPrimary) + } else { + null + }, + onClick = { if (enabled) onClick() }, + onLongClick = { if (enabled) onLongClick() } + ), + color = animateColorAsState( + targetValue = MaterialTheme.colorScheme.primary.copy(alpha = if (enabled) 1f else 0.5f) + ).value, + contentColor = MaterialTheme.colorScheme.surface + ) { + Row( + modifier = Modifier + .defaultMinSize( + minWidth = ButtonDefaults.MinWidth, + minHeight = ButtonDefaults.MinHeight + ) + .padding(4.dp) + .padding(contentPadding), + horizontalArrangement = Arrangement.spacedBy(2.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (position == CSButtonPosition.Start) { + Icon(imageVector = icon, contentDescription = null) + } + Column( + horizontalAlignment = when (position) { + CSButtonPosition.Start -> Alignment.End + CSButtonPosition.End -> Alignment.Start + } + ) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = "Hold to skip", + style = MaterialTheme.typography.labelSmall, + fontWeight = FontWeight.Normal + ) + } + if (position == CSButtonPosition.End) { + Icon(imageVector = icon, contentDescription = null) + } + } + } +} diff --git a/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/SensorItem.kt b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/SensorItem.kt new file mode 100644 index 0000000..ae6d5bc --- /dev/null +++ b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/SensorItem.kt @@ -0,0 +1,118 @@ +package com.mutualmobile.sample.ui.screens.sensorlist.components + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.paint +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import kotlin.math.absoluteValue + +@Composable +fun SensorItem( + name: String, + scrollProgress: State, + @DrawableRes imageRef: Int, + sensorValues: Map, + isAvailable: Boolean +) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Surface( + modifier = Modifier.scale(1f - scrollProgress.value.times(0.2f)), + tonalElevation = 8.dp, + shadowElevation = 16.dp, + shape = MaterialTheme.shapes.extraLarge + ) { + Column( + modifier = Modifier.padding(16.dp).alpha(if (isAvailable) 1f else 0.5f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = name, + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center + ) + Text( + if (isAvailable) "(Available)" else "(Unavailable)", + style = MaterialTheme.typography.labelMedium, + color = if (isAvailable) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.error + } + ) + Box( + modifier = Modifier + .padding(24.dp) + .clip(MaterialTheme.shapes.large) + .fillMaxHeight(0.4f) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .scale(1.25f + scrollProgress.value.absoluteValue.times(0.25f)) + .paint( + painter = painterResource(id = imageRef), + contentScale = ContentScale.Crop, + colorFilter = ColorFilter.colorMatrix( + ColorMatrix().apply { + if (!isAvailable) { + setToSaturation(0f) + } + } + ) + ) + ) + } + LazyColumn( + modifier = Modifier.heightIn( + max = LocalConfiguration.current.screenHeightDp.dp.times(0.1f) + ) + ) { + val sensorValueList = sensorValues.entries.toList() + if (sensorValueList.size > 3) { + item { + Text( + modifier = Modifier.padding(start = 8.dp), + text = "Scroll down to see more", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary + ) + } + } + items(sensorValueList.size) { index -> + val sensorValue: Map.Entry = sensorValueList[index] + SensorValue( + title = sensorValue.key, + value = sensorValue.value, + scrollProgress = scrollProgress + ) + } + } + } + } + } +} diff --git a/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/SensorValue.kt b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/SensorValue.kt new file mode 100644 index 0000000..c28c2ae --- /dev/null +++ b/sample/src/main/java/com/mutualmobile/sample/ui/screens/sensorlist/components/SensorValue.kt @@ -0,0 +1,52 @@ +package com.mutualmobile.sample.ui.screens.sensorlist.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowRight +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun SensorValue( + title: String, + value: Any, + scrollProgress: State +) { + Row( + modifier = Modifier + .padding(horizontal = 8.dp) + .padding(bottom = 4.dp) + .fillMaxWidth(0.9f), + verticalAlignment = Alignment.CenterVertically + ) { + val dashedLineColor = MaterialTheme.colorScheme.primary + Text( + modifier = Modifier.padding(end = 8.dp), + text = title, + style = MaterialTheme.typography.labelLarge + ) + Spacer(modifier = Modifier.weight(scrollProgress.value.coerceAtLeast(0.01f))) + repeat(3) { + Icon( + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = null, + tint = dashedLineColor + ) + } + Spacer(modifier = Modifier.weight(1f - scrollProgress.value.coerceAtMost(0.99f))) + Text( + modifier = Modifier.padding(start = 8.dp), + text = value.toString(), + style = MaterialTheme.typography.labelLarge + ) + } +} diff --git a/sample/src/main/java/com/mutualmobile/sample/ui/theme/Theme.kt b/sample/src/main/java/com/mutualmobile/sample/ui/theme/Theme.kt index 25e4a88..306c2fe 100644 --- a/sample/src/main/java/com/mutualmobile/sample/ui/theme/Theme.kt +++ b/sample/src/main/java/com/mutualmobile/sample/ui/theme/Theme.kt @@ -1,6 +1,7 @@ package com.mutualmobile.sample.ui.theme import android.app.Activity +import android.graphics.Color import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme @@ -10,7 +11,6 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat @@ -57,8 +57,12 @@ fun ComposeSensorsTheme( if (!view.isInEditMode) { SideEffect { val window = (view.context as Activity).window - window.statusBarColor = colorScheme.primary.toArgb() - WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + window.statusBarColor = Color.TRANSPARENT + window.navigationBarColor = Color.TRANSPARENT + with(WindowCompat.getInsetsController(window, view)) { + isAppearanceLightStatusBars = !darkTheme + isAppearanceLightNavigationBars = !darkTheme + } } } @@ -67,4 +71,4 @@ fun ComposeSensorsTheme( typography = Typography, content = content ) -} \ No newline at end of file +} diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml index 2b068d1..18ff66f 100644 --- a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -2,29 +2,147 @@ xmlns:aapt="http://schemas.android.com/aapt" android:width="108dp" android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - + android:viewportWidth="498" + android:viewportHeight="538"> + + + + + + + + + + + + + - - - - + + + + - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/drawable/accelerometer.webp b/sample/src/main/res/drawable/accelerometer.webp new file mode 100644 index 0000000..6b798be Binary files /dev/null and b/sample/src/main/res/drawable/accelerometer.webp differ diff --git a/sample/src/main/res/drawable/ambient_temperature.png b/sample/src/main/res/drawable/ambient_temperature.png new file mode 100644 index 0000000..53d0743 Binary files /dev/null and b/sample/src/main/res/drawable/ambient_temperature.png differ diff --git a/sample/src/main/res/drawable/app_logo.xml b/sample/src/main/res/drawable/app_logo.xml new file mode 100644 index 0000000..ec0d484 --- /dev/null +++ b/sample/src/main/res/drawable/app_logo.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/drawable/game_rotation_vector.xml b/sample/src/main/res/drawable/game_rotation_vector.xml new file mode 100644 index 0000000..5732f1b --- /dev/null +++ b/sample/src/main/res/drawable/game_rotation_vector.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/drawable/gravity.png b/sample/src/main/res/drawable/gravity.png new file mode 100644 index 0000000..5d183ff Binary files /dev/null and b/sample/src/main/res/drawable/gravity.png differ diff --git a/sample/src/main/res/drawable/gyroscope.png b/sample/src/main/res/drawable/gyroscope.png new file mode 100644 index 0000000..5b464ff Binary files /dev/null and b/sample/src/main/res/drawable/gyroscope.png differ diff --git a/sample/src/main/res/drawable/heart_rate.png b/sample/src/main/res/drawable/heart_rate.png new file mode 100644 index 0000000..0b9cde3 Binary files /dev/null and b/sample/src/main/res/drawable/heart_rate.png differ diff --git a/sample/src/main/res/drawable/hinge_angle.webp b/sample/src/main/res/drawable/hinge_angle.webp new file mode 100644 index 0000000..af4a778 Binary files /dev/null and b/sample/src/main/res/drawable/hinge_angle.webp differ diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/sample/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample/src/main/res/drawable/light.webp b/sample/src/main/res/drawable/light.webp new file mode 100644 index 0000000..021a4a9 Binary files /dev/null and b/sample/src/main/res/drawable/light.webp differ diff --git a/sample/src/main/res/drawable/linear_acceleration.png b/sample/src/main/res/drawable/linear_acceleration.png new file mode 100644 index 0000000..d5f6f6b Binary files /dev/null and b/sample/src/main/res/drawable/linear_acceleration.png differ diff --git a/sample/src/main/res/drawable/low_latency_off_body_detect.jpg b/sample/src/main/res/drawable/low_latency_off_body_detect.jpg new file mode 100644 index 0000000..0b1cf40 Binary files /dev/null and b/sample/src/main/res/drawable/low_latency_off_body_detect.jpg differ diff --git a/sample/src/main/res/drawable/magnetic_field.png b/sample/src/main/res/drawable/magnetic_field.png new file mode 100644 index 0000000..ddf6662 Binary files /dev/null and b/sample/src/main/res/drawable/magnetic_field.png differ diff --git a/sample/src/main/res/drawable/pressure.jpg b/sample/src/main/res/drawable/pressure.jpg new file mode 100644 index 0000000..f30511f Binary files /dev/null and b/sample/src/main/res/drawable/pressure.jpg differ diff --git a/sample/src/main/res/drawable/proximity.webp b/sample/src/main/res/drawable/proximity.webp new file mode 100644 index 0000000..fd8d04b Binary files /dev/null and b/sample/src/main/res/drawable/proximity.webp differ diff --git a/sample/src/main/res/drawable/rotation_vector.png b/sample/src/main/res/drawable/rotation_vector.png new file mode 100644 index 0000000..fb6474b Binary files /dev/null and b/sample/src/main/res/drawable/rotation_vector.png differ diff --git a/sample/src/main/res/drawable/splash_logo.xml b/sample/src/main/res/drawable/splash_logo.xml new file mode 100644 index 0000000..b9b8911 --- /dev/null +++ b/sample/src/main/res/drawable/splash_logo.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6f3b755..7353dbd 100644 --- a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6f3b755..7353dbd 100644 --- a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..2c212ec Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.webp b/sample/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/sample/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..57d06ce Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..19b17e9 Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.webp b/sample/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/sample/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..fe2528b Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3089fe8 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp b/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/sample/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..2e41704 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..cad875b Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..81cf453 Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..3e336de Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..72d209e Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml index f8c6127..ab6b2d2 100644 --- a/sample/src/main/res/values/colors.xml +++ b/sample/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #D0F1FF \ No newline at end of file diff --git a/sample/src/main/res/values/ic_launcher_background.xml b/sample/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..7e94b98 --- /dev/null +++ b/sample/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #D0F1FF + \ No newline at end of file diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index e7070fe..8894521 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - ComposeSensors + Compose Sensors \ No newline at end of file diff --git a/sample/src/main/res/values/themes.xml b/sample/src/main/res/values/themes.xml index 99b4d25..5468a9e 100644 --- a/sample/src/main/res/values/themes.xml +++ b/sample/src/main/res/values/themes.xml @@ -2,4 +2,9 @@ \ No newline at end of file