Update AuthenticationScreen

This commit is contained in:
Andres Gomez, Thomas (ITDV CC) - AF (ext) 2023-07-17 15:38:23 +02:00
parent fb31de8130
commit 70c25b0fc5
10 changed files with 477 additions and 40 deletions

View file

@ -84,6 +84,7 @@ dependencies {
// Compose
implementation("androidx.compose.ui:ui:1.4.3")
implementation("androidx.compose.ui:ui-util:1.4.3")
implementation("androidx.compose.ui:ui-graphics:1.4.3")
implementation("androidx.compose.ui:ui-tooling-preview:1.4.3")
implementation("androidx.compose.material:material:1.4.3")

View file

@ -2,12 +2,22 @@ package com.pixelized.rplexicon.ui.screens.authentication
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@ -18,9 +28,15 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
@ -28,16 +44,22 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.LocalActivity
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexicon
import com.pixelized.rplexicon.ui.navigation.rootOption
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexicon
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
import com.pixelized.rplexicon.utilitary.sensor.Gyroscope
import kotlinx.coroutines.CoroutineScope
import kotlin.math.E
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
@Composable
fun AuthenticationScreen(
@ -85,6 +107,9 @@ private fun AuthenticationScreenContent(
onGoogleSignIn: () -> Unit,
) {
val typography = MaterialTheme.typography
PartyBackground()
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Bottom),
@ -111,6 +136,148 @@ private fun AuthenticationScreenContent(
}
}
@Composable
private fun PartyBackground(
modifier: Modifier = Modifier,
spacerWidth: Dp = 1.dp,
images: List<Int> = rememberPortrait(),
) {
Gyroscope {
val balance = remember {
derivedStateOf {
max(-1f, min(1f, this.gyroscope.value.roll))
}
}
val colorFilter = remember {
ColorFilter.colorMatrix(
ColorMatrix().also { it.setToSaturation(0f) }
)
}
Row(modifier = modifier) {
Image(
modifier = Modifier
.fillMaxHeight()
.weight(weight = animatedWeight(progress = balance, divergence = 0.95f, position = 1f)),
contentScale = ContentScale.FillHeight,
colorFilter = colorFilter,
painter = painterResource(id = images[0]),
contentDescription = null,
)
Spacer(
modifier = Modifier
.width(width = spacerWidth)
.fillMaxHeight()
.background(color = MaterialTheme.colorScheme.surface)
)
Image(
modifier = Modifier
.fillMaxHeight()
.weight(weight = animatedWeight(progress = balance, divergence = 0.93f, position = .5f)),
contentScale = ContentScale.FillHeight,
colorFilter = colorFilter,
painter = painterResource(id = images[1]),
contentDescription = null,
)
Spacer(
modifier = Modifier
.width(width = spacerWidth)
.fillMaxHeight()
.background(color = MaterialTheme.colorScheme.surface)
)
Image(
modifier = Modifier
.fillMaxHeight()
.weight(weight = animatedWeight(progress = balance, divergence = 0.91f, position = 0f)),
contentScale = ContentScale.FillHeight,
colorFilter = colorFilter,
painter = painterResource(id = images[2]),
contentDescription = null,
)
Spacer(
modifier = Modifier
.width(width = spacerWidth)
.fillMaxHeight()
.background(color = MaterialTheme.colorScheme.surface)
)
Image(
modifier = Modifier
.fillMaxHeight()
.weight(weight = animatedWeight(progress = balance, divergence = 0.93f, position = -.5f)),
contentScale = ContentScale.FillHeight,
colorFilter = colorFilter,
painter = painterResource(id = images[3]),
contentDescription = null,
)
Spacer(
modifier = Modifier
.width(width = spacerWidth)
.fillMaxHeight()
.background(color = MaterialTheme.colorScheme.surface)
)
Image(
modifier = Modifier
.fillMaxHeight()
.weight(weight = animatedWeight(progress = balance, divergence = 0.95f, position = -1f)),
contentScale = ContentScale.FillHeight,
colorFilter = colorFilter,
painter = painterResource(id = images[4]),
contentDescription = null,
)
}
Box(
modifier = Modifier
.fillMaxSize()
.background(brush = rememberBackgroundGradient())
)
}
}
@Composable
private fun rememberPortrait(): List<Int> = remember {
listOf(
R.drawable.im_tigrane,
R.drawable.im_unathana,
R.drawable.im_brulkhai,
R.drawable.im_nelia,
R.drawable.im_leandre,
)
}
@Composable
private fun animatedWeight(
progress: State<Float>,
amplitude: Float = E.toFloat(),
divergence: Float = 0.95f,
position: Float,
step: Float = .16f,
): Float {
val animatedWeight = animateFloatAsState(
targetValue = divergence * amplitude.pow(-(position - progress.value).pow(2f) / step) + (1f - divergence),
label = "AnimatedBackgroundWeight",
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessLow,
)
)
return animatedWeight.value
}
@Composable
private fun rememberBackgroundGradient(): Brush {
val colorScheme = MaterialTheme.colorScheme
return remember {
Brush.verticalGradient(
colors = listOf(
colorScheme.surface.copy(alpha = 0.25f),
colorScheme.surface.copy(alpha = 0.5f),
colorScheme.surface.copy(alpha = 0.75f),
colorScheme.surface.copy(alpha = 1.0f),
colorScheme.surface.copy(alpha = 1.0f),
)
)
}
}
@Composable
private fun rememeberGoogleStringResource(): AnnotatedString {
val default = LocalTextStyle.current.toSpanStyle()

View file

@ -7,6 +7,10 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.with
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@ -118,8 +122,9 @@ fun LexiconScreen(
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class
@OptIn(
ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class, ExperimentalAnimationApi::class
)
@Composable
private fun LexiconScreenContent(
@ -141,7 +146,7 @@ private fun LexiconScreenContent(
},
)
},
floatingActionButton = {
// floatingActionButton = {
// FloatingActionButton(
// modifier = Modifier.padding(start = 32.dp),
// expended = isFabExpended.value,
@ -162,50 +167,63 @@ private fun LexiconScreenContent(
// )
// },
// )
}
// }
) { padding ->
Box(
modifier = Modifier.padding(paddingValues = padding),
contentAlignment = Alignment.TopCenter,
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state = refreshState),
state = lazyColumnState,
contentPadding = PaddingValues(
top = 8.dp,
bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
),
) {
if (items.value.isEmpty()) {
items(
count = 6,
key = { it },
contentType = { "Lexicon" },
AnimatedContent(
targetState = items.value.isEmpty(),
transitionSpec = { fadeIn() with fadeOut() },
label = "AnimatedLexicon"
) { empty ->
when (empty) {
true -> LazyColumn(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state = refreshState),
state = lazyColumnState,
contentPadding = PaddingValues(
top = 8.dp,
bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
),
) {
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
item = LexiconItemUio.Brulkhai,
)
items(count = 6) {
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
item = LexiconItemUio.Brulkhai,
)
}
}
} else {
items(
items = items.value,
key = { it.id },
contentType = { "Lexicon" },
else -> LazyColumn(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state = refreshState),
state = lazyColumnState,
contentPadding = PaddingValues(
top = 8.dp,
bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
),
) {
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.clickable { onItem(it) }
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
item = it,
)
items(
items = items.value,
key = { it.id },
contentType = { "Lexicon" },
) {
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.clickable { onItem(it) }
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
item = it,
)
}
}
}
}

View file

@ -0,0 +1,187 @@
package com.pixelized.rplexicon.utilitary.sensor
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.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import kotlin.math.cos
import kotlin.math.sin
@Composable
fun Gyroscope(
content: @Composable GyroscopeScope.() -> Unit,
) {
val context = LocalContext.current
val gyroscope = remember {
mutableStateOf(GyroscopeData.Zero)
}
val scope = remember {
GyroscopeScopeImpl(
gyroscope = gyroscope,
initialAngle = GyroscopeScopeImpl.INITIAL_ANGLE_X,
)
}
if (LocalInspectionMode.current.not()) {
val onSensorChanged = remember {
object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == Sensor.TYPE_GRAVITY) {
scope.gravity = event.values
}
if (event?.sensor?.type == Sensor.TYPE_MAGNETIC_FIELD) {
scope.geomagnetic = event.values
}
val orientation = getGyroscopeData(
gravity = scope.gravity,
geomagnetic = scope.geomagnetic,
deviceRotationMatrix = scope.deviceRotationMatrix,
rotationMatrix = scope.localRotationMatrix,
localRotationMatrix = scope.localOrientationMatrix,
orientationMatrix = scope.orientationMatrix,
)
if (orientation != null) {
gyroscope.value = orientation
}
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit
}
}
DisposableEffect(key1 = "SensorServices") {
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
val magnetometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
sensorManager.registerListener(
onSensorChanged,
gravitySensor,
SensorManager.SENSOR_DELAY_UI,
)
sensorManager.registerListener(
onSensorChanged,
magnetometerSensor,
SensorManager.SENSOR_DELAY_UI,
)
onDispose {
sensorManager.unregisterListener(onSensorChanged)
}
}
}
scope.content()
}
//////////////////////////////////////
// region: GyroscopeScope
@Immutable
interface GyroscopeScope {
val gyroscope: State<GyroscopeData>
}
@Immutable
class GyroscopeScopeImpl(
override val gyroscope: State<GyroscopeData>,
initialAngle: Float,
) : GyroscopeScope {
var gravity: FloatArray? = null
var geomagnetic: FloatArray? = null
val deviceRotationMatrix: FloatArray = FloatArray(9)
val orientationMatrix: FloatArray = FloatArray(3)
val localRotationMatrix: FloatArray = getRotationMatrix(angle = initialAngle)
val localOrientationMatrix: FloatArray = FloatArray(9)
// https://fr.wikipedia.org/wiki/Matrice_de_rotation
private fun getRotationMatrix(angle: Float): FloatArray {
return floatArrayOf(1f, 0f, 0f, 0f, cos(angle), -sin(angle), 0f, sin(angle), cos(angle))
}
companion object {
const val INITIAL_ANGLE_X: Float = -Math.PI.toFloat() / 6f
}
}
// endregion
//////////////////////////////////////
// region: Helper method.
/**
* Helper method to build a [GyroscopeData] instance containing the computation Pitch and Roll.
*
* @param gravity a FloatArray? of 3 elements representing the gravity acceleration.
* @param geomagnetic a FloatArray? of 3 elements representing the magnetosphere acceleration.
* @param deviceRotationMatrix a FloatArray of 9 elements representing the device rotation.
* @param rotationMatrix a FloatArray of 9 elements representing the wanted initial rotation.
* @param localRotationMatrix a FloatArray of 9 elements representing the device rotation with the initial angle set.
* @param orientationMatrix a FloatArray of 3 elements representing the Azimuth, Pitch and Roll.
*/
private fun getGyroscopeData(
gravity: FloatArray?,
geomagnetic: FloatArray?,
deviceRotationMatrix: FloatArray,
rotationMatrix: FloatArray,
localRotationMatrix: FloatArray,
orientationMatrix: FloatArray,
): GyroscopeData? {
if (gravity != null && geomagnetic != null) {
// populate the rotation matrix.
if (SensorManager.getRotationMatrix(deviceRotationMatrix, null, gravity, geomagnetic)) {
// rotate the orientation matrix
rotate(
a = deviceRotationMatrix,
b = rotationMatrix,
out = localRotationMatrix,
)
// populate the orientation matrix.
SensorManager.getOrientation(localRotationMatrix, orientationMatrix)
// return the orientation.
return GyroscopeData(
pitch = orientationMatrix[1],
roll = orientationMatrix[2],
)
}
}
return null
}
/**
* This method is use to introduce an initial angle for the gyroscope by multiplying
* [a] by [b] and store the result into [out].
*
* @param a a FloatArray of length 9
* @param b a FloatArray of length 9
* @param out a FloatArray of length 9
*/
private fun rotate(
a: FloatArray,
b: FloatArray,
out: FloatArray,
) {
if (a.size == 9 && b.size == 9 && out.size == 9) {
out[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]
out[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]
out[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]
out[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]
out[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]
out[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]
out[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]
out[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]
out[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]
}
}
// endregion

View file

@ -0,0 +1,64 @@
package com.pixelized.rplexicon.utilitary.sensor
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.util.packFloats
import androidx.compose.ui.util.unpackFloat1
import androidx.compose.ui.util.unpackFloat2
@Stable
fun GyroscopeData(pitch: Float, roll: Float): GyroscopeData =
GyroscopeData(packFloats(pitch, roll))
@Immutable
@JvmInline
value class GyroscopeData internal constructor(@PublishedApi internal val packedValue: Long) {
@Stable
val pitch: Float
get() = unpackFloat1(packedValue)
@Stable
val roll: Float
get() = unpackFloat2(packedValue)
@Stable
operator fun component1(): Float = pitch
@Stable
operator fun component2(): Float = roll
@Suppress("unused")
fun copy(pitch: Float = this.pitch, roll: Float = this.roll): GyroscopeData =
GyroscopeData(pitch = pitch, roll = roll)
@Stable
operator fun minus(other: GyroscopeData): GyroscopeData =
GyroscopeData(pitch = pitch - other.pitch, roll = roll - other.roll)
@Stable
operator fun plus(other: GyroscopeData): GyroscopeData =
GyroscopeData(pitch = pitch + other.pitch, roll = roll + other.roll)
@Stable
operator fun unaryMinus(): GyroscopeData =
GyroscopeData(pitch = -pitch, roll = -roll)
@Stable
operator fun times(operand: Float): GyroscopeData =
GyroscopeData(pitch = pitch * operand, roll = roll * operand)
@Stable
operator fun div(operand: Float): GyroscopeData =
GyroscopeData(pitch = pitch / operand, roll = roll / operand)
@Stable
operator fun rem(operand: Int): GyroscopeData =
GyroscopeData(pitch = pitch % operand, roll = roll % operand)
@Stable
override fun toString(): String = "(pitch:$pitch, roll:$roll)"
companion object {
val Zero = GyroscopeData(0f, 0f)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB