Skip to content

Commit

Permalink
Merge pull request #750 from shunm-999/feature/holographic-effect-card
Browse files Browse the repository at this point in the history
Make profile card glow based on device orientation
  • Loading branch information
takahirom authored Aug 23, 2024
2 parents 60c0e06 + 214765c commit 0b2067c
Show file tree
Hide file tree
Showing 6 changed files with 415 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.github.droidkaigi.confsched.ui

import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext

@Composable
internal actual fun getOrientationSensorManager(
onOrientationChanged: (Orientation) -> Unit,
): OrientationSensorManager {
val context = LocalContext.current
return remember(context, onOrientationChanged) {
AndroidOrientationSensorManager(
context = context,
onOrientationChanged = onOrientationChanged,
)
}
}

internal class AndroidOrientationSensorManager(
context: Context,
override val onOrientationChanged: (Orientation) -> Unit,
) : OrientationSensorManager, SensorEventListener {
private val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager

private val accelerometerReading = FloatArray(3)
private val magnetometerReading = FloatArray(3)

private val rotationMatrix = FloatArray(9)
private val orientationAngles = FloatArray(3)

override fun start() {
sensorManager.registerListener(
this,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SENSOR_DELAY,
)
sensorManager.registerListener(
this,
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SENSOR_DELAY,
)
}

override fun stop() {
sensorManager.unregisterListener(this)
}

override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
// Do nothing
}

override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> {
System.arraycopy(
event.values,
0,
accelerometerReading,
0,
accelerometerReading.size,
)
}

Sensor.TYPE_MAGNETIC_FIELD -> {
System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
}
}
updateOrientationAngles()

onOrientationChanged(
Orientation(
azimuth = orientationAngles[0],
pitch = orientationAngles[1],
roll = orientationAngles[2],
),
)
}

private fun updateOrientationAngles() {
SensorManager.getRotationMatrix(
rotationMatrix,
null,
accelerometerReading,
magnetometerReading,
)
SensorManager.getOrientation(rotationMatrix, orientationAngles)
}

companion object {
private const val SENSOR_DELAY: Int = SensorManager.SENSOR_DELAY_UI
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.droidkaigi.confsched.ui

import androidx.compose.runtime.Composable

data class Orientation(
val azimuth: Float,
val pitch: Float,
val roll: Float,
) {
companion object {
val Zero = Orientation(0f, 0f, 0f)
}
}

internal interface OrientationSensorManager {
val onOrientationChanged: (Orientation) -> Unit
fun start()
fun stop()
}

@Composable
internal expect fun getOrientationSensorManager(
onOrientationChanged: (Orientation) -> Unit,
): OrientationSensorManager
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.github.droidkaigi.confsched.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner

interface DeviceOrientationScope {
val orientation: Orientation
}

private class DeviceOrientationScopeImpl : DeviceOrientationScope {
override var orientation: Orientation by mutableStateOf(Orientation.Zero)
private set

fun updateOrientation(orientation: Orientation) {
this.orientation = orientation
}
}

@Composable
fun WithDeviceOrientation(
content: @Composable (DeviceOrientationScope.() -> Unit),
) {
val lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current

val scope = remember {
DeviceOrientationScopeImpl()
}
val sensorManager = getOrientationSensorManager {
scope.updateOrientation(it)
}

DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
sensorManager.start()
} else if (event == Lifecycle.Event.ON_PAUSE) {
sensorManager.stop()
}
}

lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}

content(scope)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.github.droidkaigi.confsched.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import platform.CoreMotion.CMMotionManager
import platform.Foundation.NSOperationQueue

@Composable
internal actual fun getOrientationSensorManager(
onOrientationChanged: (Orientation) -> Unit,
): OrientationSensorManager {
return remember(onOrientationChanged) {
IosOrientationSensorManager(
onOrientationChanged = onOrientationChanged,
)
}
}

internal class IosOrientationSensorManager(
override val onOrientationChanged: (Orientation) -> Unit,
) : OrientationSensorManager {
private val motionManager = CMMotionManager()

override fun start() {
if (!motionManager.deviceMotionActive) {
return
}
NSOperationQueue.currentQueue()?.let {
motionManager.startDeviceMotionUpdatesToQueue(
it,
) { motion, _ ->
if (motion == null) {
return@startDeviceMotionUpdatesToQueue
}
onOrientationChanged(
Orientation(
azimuth = motion.attitude.yaw.toFloat(),
pitch = motion.attitude.pitch.toFloat(),
roll = motion.attitude.roll.toFloat(),
),
)
}
}
}

override fun stop() {
if (!motionManager.deviceMotionActive) {
return
}
motionManager.stopDeviceMotionUpdates()
}
}
Loading

0 comments on commit 0b2067c

Please sign in to comment.