package eu.wa5p.lightmeter import android.content.Context import android.hardware.Sensor import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import eu.wa5p.lightmeter.ui.theme.LightMeterTheme import kotlin.math.roundToInt const val TAG: String = "lightmeter MainActivity" class MainActivity : ComponentActivity(), SensorEventListener { private lateinit var sensorManager: SensorManager private var illuminanceSensor: Sensor? = null private var lightmeterState = mutableStateOf( LightmeterState( illuminance = 0.0, aperture = 1.8, iso = 100, shutterSpeed = 0.01 ) ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { LightMeterTheme { LightmeterHomeScreen(state = lightmeterState ) } } sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager illuminanceSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) } private fun updateLightmeterState(illuminance: Double) { val iso = lightmeterState.value.iso val aperture = lightmeterState.value.aperture val shutterSpeed = (aperture * aperture) / (illuminance * (iso / 100)) lightmeterState.value = lightmeterState.value.copy( illuminance = illuminance, iso = iso, aperture = aperture, shutterSpeed = shutterSpeed ) } override fun onSensorChanged(event: SensorEvent?) { event?.values?.let { for (v in it) { Log.d(TAG, "sensor changed: $v lux") updateLightmeterState(v.toDouble()) } } } override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { Log.d(TAG, "accuracy changed: sensor: ${sensor?.name}. accuracy: $accuracy") } override fun onResume() { super.onResume() sensorManager.registerListener(this, illuminanceSensor, SensorManager.SENSOR_DELAY_NORMAL) } override fun onPause() { super.onPause() sensorManager.unregisterListener(this) } override fun onDestroy() { super.onDestroy() sensorManager.unregisterListener(this) } } @Composable fun LightmeterListItem( label: String, value: String, unit: String ) { Row(modifier = Modifier.padding(bottom = 8.dp)) { Text( modifier = Modifier .weight(0.4f) .padding(all = 8.dp), text = label ) Text( modifier = Modifier .weight(0.4f) .padding(all = 8.dp), textAlign = TextAlign.Right, text = value ) Text( modifier = Modifier .weight(0.2f) .padding(all = 8.dp), text = unit ) } } @Preview(showBackground = true) @Composable fun LightmeterListItemPreview() { LightMeterTheme { LightmeterListItem( label = "Illuminance", value = "1234", unit = "lux" ) } } // TODO: Recalculate state on change @Composable fun IsoSlider(state: MutableState) { Row( modifier = Modifier.padding(all = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier.weight(0.3f), text = "ISO" ) Slider( modifier = Modifier.weight(0.7f), value = state.value.iso.toFloat(), onValueChange = { value -> state.value = state.value.copy(iso = value.roundToInt()) }, valueRange = 1f..2000f ) } } val previewlightmeterState = mutableStateOf( LightmeterState( illuminance = 0.0, aperture = 1.8, iso = 100, shutterSpeed = 0.01 ) ) @Preview(showBackground = true) @Composable fun IsoSliderPreview() { LightMeterTheme { IsoSlider( state = remember { previewlightmeterState } ) } } // TODO: Recalculate state on change @Composable fun ApertureSlider(state: MutableState) { Row( modifier = Modifier.padding(all = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Text( modifier = Modifier.weight(0.3f), text = "Aperture" ) Slider( modifier = Modifier.weight(0.7f), value = state.value.aperture.toFloat(), onValueChange = { value -> state.value = state.value.copy(aperture = value.toDouble()) }, valueRange = 0.85f..22f ) } } @Preview(showBackground = true) @Composable fun ApertureSliderPreview() { LightMeterTheme { ApertureSlider(state = remember { previewlightmeterState }) } } @Composable fun LightmeterHomeScreen(state: MutableState) { Column(modifier = Modifier .fillMaxSize() .padding(top = 64.dp) .padding(all = 32.dp) ) { Column( verticalArrangement = Arrangement.Top ) { LightmeterListItem( label = "Illuminance", value = "%.2f".format(state.value.illuminance), unit = "lux" ) LightmeterListItem( label = "ISO", value = state.value.iso.toString(), unit = "" ) LightmeterListItem( label = "Aperture", value = "%.2f".format(state.value.aperture), unit = "f" ) LightmeterListItem( label = "Shutter Speed", value = "%.2f".format(state.value.shutterSpeed), unit = "s" ) } Column( modifier = Modifier.padding(top = 64.dp), verticalArrangement = Arrangement.Bottom ) { IsoSlider(state = state) ApertureSlider(state = state) } } } @Preview(showBackground = true) @Composable fun LightmeterHomeScreenPreview() { LightMeterTheme { LightmeterHomeScreen( state = remember { previewlightmeterState } ) } }