Skip to content

Commit 7d5c386

Browse files
authored
Merge pull request #17231 from wordpress-mobile/issue/17214-final-design-touces
[Revamp Landing Screen] Jetpack Landing Screen design tweaks
2 parents b12c5f2 + 9f5e448 commit 7d5c386

17 files changed

Lines changed: 168 additions & 120 deletions

File tree

WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/LoginPrologueRevampedViewModel.kt

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,71 +11,88 @@ import androidx.lifecycle.ViewModel
1111
import dagger.hilt.android.lifecycle.HiltViewModel
1212
import dagger.hilt.android.qualifiers.ApplicationContext
1313
import javax.inject.Inject
14-
import kotlin.math.exp
15-
import kotlin.math.ln
14+
import kotlin.math.PI
1615

17-
// This factor is used to convert the raw values emitted from device sensor to an appropriate scale for the consuming
18-
// composables.
19-
private const val ACCELERATION_FACTOR = -0.1f
16+
/**
17+
* This factor is used to convert the raw values emitted from device sensor to an appropriate scale for the consuming
18+
* composables. The range for pitch values is -π/2 to π/2, representing an upright pose and an upside-down pose,
19+
* respectively. E.g. Setting this factor to 0.2 / (π/2) will scale this output range to -0.2 to 0.2. The resulting
20+
* product is interpreted in units proportional to the repeating child composable's height per second, i.e. at a
21+
* velocity of 0.1, it will take 10 seconds for the looping animation to repeat. When applied, the product can also be
22+
* thought of as a frequency value in Hz.
23+
*/
24+
private const val VELOCITY_FACTOR = (0.2 / (PI / 2)).toFloat()
2025

21-
// The maximum velocity (in either direction)
22-
private const val MAXIMUM_VELOCITY = 0.125f
23-
24-
// The velocity decay factor (i.e. 1/4th velocity after 1 second)
25-
private val VELOCITY_DECAY = -ln(4f)
26-
27-
// An additional acceleration applied to make the text scroll when the device is flat on a table
28-
private const val DRIFT = -0.05f
26+
/**
27+
* This is the default pitch provided for devices lacking support for the sensors used. This is consumed by the model
28+
* as a value in radians, but is specified here as -30 degrees for convenience. This represents a device pose that is
29+
* slightly upright from flat, approximating a typical usage pattern, and will ensure that the text is animated at
30+
* an appropriate velocity when sensors are unavailable.
31+
*/
32+
private const val DEFAULT_PITCH = (-30 * PI / 180).toFloat()
2933

3034
@HiltViewModel
3135
class LoginPrologueRevampedViewModel @Inject constructor(
3236
@ApplicationContext appContext: Context,
3337
) : ViewModel() {
34-
private var acceleration = -0.3f // Default value when sensor is off
35-
private var velocity = 0f
38+
private val accelerometerData = FloatArray(3)
39+
private val magnetometerData = FloatArray(3)
40+
private val rotationMatrix = FloatArray(9)
41+
private val orientationAngles = floatArrayOf(0f, DEFAULT_PITCH, 0f)
3642
private var position = 0f
3743

3844
/**
3945
* This function updates the physics model for the interactive animation by applying the elapsed time (in seconds)
4046
* to update the velocity and position.
4147
*
42-
* * Velocity is constrained so that it does not fall below -MAXIMUM_VELOCITY and does not exceed MAXIMUM_VELOCITY.
48+
* * Velocity is calculated as proportional to the pitch angle
4349
* * Position is constrained so that it always falls between 0 and 1, and represents the relative vertical offset
4450
* in terms of the height of the repeated child composable.
4551
*
4652
* @param elapsed the elapsed time (in seconds) since the last frame
4753
*/
4854
fun updateForFrame(elapsed: Float) {
49-
// Update the velocity, (decayed and clamped to the maximum)
50-
velocity = (velocity * exp(elapsed * VELOCITY_DECAY) + elapsed * acceleration)
51-
.coerceIn(-MAXIMUM_VELOCITY, MAXIMUM_VELOCITY)
52-
// Update the position, modulo 1 (ensuring a value greater or equal to 0, and less than 1)
53-
position = ((position + elapsed * velocity) % 1 + 1) % 1
55+
orientationAngles.let { (_, pitch) ->
56+
val velocity = pitch * VELOCITY_FACTOR
5457

55-
_positionData.postValue(position)
58+
// Update the position, modulo 1 (ensuring a value greater or equal to 0, and less than 1)
59+
position = ((position + elapsed * velocity) % 1 + 1) % 1
60+
61+
_positionData.postValue(position)
62+
}
5663
}
5764

58-
/** This LiveData responds to accelerometer data from the y-axis of the device and emits updated position data. */
65+
/**
66+
* This LiveData responds to orientation data to calculate the pitch of the device. This is then used to update the
67+
* velocity and position for each frame.
68+
*/
5969
private val _positionData = object : MutableLiveData<Float>(), SensorEventListener {
6070
private val sensorManager
6171
get() = appContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
6272

6373
override fun onActive() {
6474
super.onActive()
65-
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
66-
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME)
75+
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also {
76+
sensorManager.registerListener( this, it, SensorManager.SENSOR_DELAY_GAME)
77+
}
78+
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also {
79+
sensorManager.registerListener( this, it, SensorManager.SENSOR_DELAY_GAME)
80+
}
6781
}
6882

6983
override fun onInactive() {
7084
super.onInactive()
7185
sensorManager.unregisterListener(this)
7286
}
7387

74-
override fun onSensorChanged(event: SensorEvent?) {
75-
event?.values?.let { (_, yAxisAcceleration, _) ->
76-
acceleration = yAxisAcceleration * ACCELERATION_FACTOR + DRIFT
77-
postValue(position)
88+
override fun onSensorChanged(event: SensorEvent) {
89+
when (event.sensor.type) {
90+
Sensor.TYPE_ACCELEROMETER -> event.values.copyInto(accelerometerData)
91+
Sensor.TYPE_MAGNETIC_FIELD -> event.values.copyInto(magnetometerData)
7892
}
93+
// Update the orientation angles when sensor data is updated
94+
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerData, magnetometerData)
95+
SensorManager.getOrientation(rotationMatrix, orientationAngles)
7996
}
8097

8198
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit

WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/ColumnWithFrostedGlassBackground.kt

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import androidx.compose.ui.graphics.Outline.Rectangle
1919
import androidx.compose.ui.graphics.Shape
2020
import androidx.compose.ui.graphics.SolidColor
2121
import androidx.compose.ui.layout.SubcomposeLayout
22-
import androidx.compose.ui.platform.LocalDensity
2322
import androidx.compose.ui.res.colorResource
2423
import androidx.compose.ui.unit.Density
2524
import androidx.compose.ui.unit.LayoutDirection
@@ -28,6 +27,8 @@ import org.wordpress.android.R
2827
import org.wordpress.android.ui.accounts.login.components.SlotsEnum.Buttons
2928
import org.wordpress.android.ui.accounts.login.components.SlotsEnum.ClippedBackground
3029

30+
private val blurRadius = 30.dp
31+
3132
/**
3233
* These slots are utilized below in a subcompose layout in order to measure the size of the buttons composable. The
3334
* measured height is then used to create a clip shape for the blurred background layer. This allows the background
@@ -37,23 +38,14 @@ private enum class SlotsEnum { Buttons, ClippedBackground }
3738

3839
@Composable
3940
private fun ColumnWithTopGlassBorder(
40-
modifier: Modifier = Modifier,
4141
content: @Composable ColumnScope.() -> Unit
4242
) {
4343
Column(
44-
modifier = modifier
45-
.background(
46-
brush = SolidColor(colorResource(R.color.bg_jetpack_login_splash_bottom_panel)),
47-
alpha = 0.6f
48-
)
44+
modifier = Modifier.background(SolidColor(colorResource(R.color.bg_jetpack_login_splash_bottom_panel)))
4945
) {
5046
Divider(
5147
thickness = 1.dp,
52-
color = colorResource(R.color.border_shadow_jetpack_login_splash_bottom_panel),
53-
)
54-
Divider(
55-
thickness = 1.dp,
56-
color = colorResource(R.color.border_highlight_jetpack_login_splash_bottom_panel),
48+
color = colorResource(R.color.border_top_jetpack_login_splash_bottom_panel),
5749
)
5850
content()
5951
}
@@ -64,8 +56,6 @@ fun ColumnWithFrostedGlassBackground(
6456
background: @Composable (modifier: Modifier, textModifier: Modifier) -> Unit,
6557
content: @Composable () -> Unit,
6658
) {
67-
val topBorderHeight = with(LocalDensity.current) { 1.dp.toPx() }
68-
6959
SubcomposeLayout { constraints ->
7060
val buttonsPlaceables = subcompose(Buttons) {
7161
ColumnWithTopGlassBorder {
@@ -81,7 +71,7 @@ fun ColumnWithFrostedGlassBackground(
8171
bottom = size.height,
8272
left = 0f,
8373
right = size.width,
84-
top = size.height - buttonsHeight + topBorderHeight,
74+
top = size.height - buttonsHeight,
8575
)
8676
)
8777
}
@@ -92,11 +82,11 @@ fun ColumnWithFrostedGlassBackground(
9282
modifier = Modifier.clip(buttonsClipShape),
9383
textModifier = Modifier.composed {
9484
if (VERSION.SDK_INT >= VERSION_CODES.S) {
95-
blur(15.dp, BlurredEdgeTreatment.Unbounded)
85+
blur(blurRadius, BlurredEdgeTreatment.Unbounded)
9686
} else {
9787
// On versions older than Android 12 the blur modifier is not supported,
9888
// so we make the text transparent to have the buttons stand out.
99-
alpha(0.25f)
89+
alpha(0.05f)
10090
}
10191
}
10292
)

WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/LoopingText.kt

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,58 @@
11
package org.wordpress.android.ui.accounts.login.components
22

3-
import androidx.compose.foundation.layout.Spacer
4-
import androidx.compose.foundation.layout.height
3+
import android.content.res.Configuration
54
import androidx.compose.material.Text
65
import androidx.compose.runtime.Composable
76
import androidx.compose.ui.Modifier
8-
import androidx.compose.ui.graphics.Color
97
import androidx.compose.ui.res.colorResource
108
import androidx.compose.ui.res.stringArrayResource
9+
import androidx.compose.ui.text.ParagraphStyle
10+
import androidx.compose.ui.text.SpanStyle
1111
import androidx.compose.ui.text.TextStyle
12+
import androidx.compose.ui.text.buildAnnotatedString
1213
import androidx.compose.ui.text.font.FontWeight
14+
import androidx.compose.ui.text.withStyle
1315
import androidx.compose.ui.tooling.preview.Devices
1416
import androidx.compose.ui.tooling.preview.Preview
15-
import androidx.compose.ui.unit.dp
1617
import androidx.compose.ui.unit.sp
1718
import org.wordpress.android.R
1819
import org.wordpress.android.ui.accounts.login.LocalPosition
1920
import org.wordpress.android.ui.compose.theme.AppTheme
2021
import org.wordpress.android.util.extensions.isOdd
2122

22-
private val fontSize = 43.sp
23-
private val lineHeight = fontSize * 0.95
24-
25-
@Composable
26-
private fun LargeText(
27-
text: String,
28-
color: Color,
29-
modifier: Modifier = Modifier,
30-
) {
31-
Text(
32-
text = text,
33-
style = TextStyle(
34-
fontSize = fontSize,
35-
fontWeight = FontWeight.SemiBold,
36-
letterSpacing = (-0.3).sp,
37-
lineHeight = lineHeight,
38-
),
39-
color = color,
40-
modifier = modifier
41-
)
42-
}
23+
private val fontSize = 40.sp
24+
private val lineHeight = fontSize / 100 * 105 // last value = % of fontSize
4325

4426
@Composable
4527
private fun LargeTexts() {
4628
val texts = stringArrayResource(R.array.login_prologue_revamped_jetpack_feature_texts)
4729

48-
texts.forEachIndexed { index, text ->
49-
LargeText(
50-
text = text,
51-
color = when (index.isOdd) {
52-
true -> colorResource(R.color.text_color_jetpack_login_feature_odd)
53-
false -> colorResource(R.color.text_color_jetpack_login_feature_even)
30+
val secondaryColor = colorResource(R.color.text_color_jetpack_login_label_secondary)
31+
val primaryColor = colorResource(R.color.text_color_jetpack_login_label_primary)
32+
33+
val styledText = buildAnnotatedString {
34+
texts.forEachIndexed { index, text ->
35+
withStyle(ParagraphStyle(lineHeight = lineHeight)) {
36+
when ((index + 1).isOdd) {
37+
true -> withStyle(SpanStyle(color = secondaryColor)) {
38+
append(text)
39+
}
40+
false -> withStyle(SpanStyle(color = primaryColor)) {
41+
append(text)
42+
}
5443
}
55-
)
56-
Spacer(modifier = Modifier.height(2.dp))
44+
}
45+
}
5746
}
47+
48+
Text(
49+
text = styledText,
50+
style = TextStyle(
51+
fontSize = fontSize,
52+
fontWeight = FontWeight.Bold,
53+
letterSpacing = (-0.03).sp,
54+
),
55+
)
5856
}
5957

6058
@Composable
@@ -67,7 +65,8 @@ fun LoopingText(modifier: Modifier = Modifier) {
6765
}
6866
}
6967

70-
@Preview(showBackground = true, device = Devices.PIXEL_4_XL)
68+
@Preview(showBackground = true, device = Devices.PIXEL_4)
69+
@Preview(showBackground = true, device = Devices.PIXEL_4, uiMode = Configuration.UI_MODE_NIGHT_YES)
7170
@Composable
7271
fun PreviewLoopingText() {
7372
AppTheme {
Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
package org.wordpress.android.ui.accounts.login.components
22

3+
import android.content.res.Configuration
34
import androidx.compose.foundation.Image
45
import androidx.compose.foundation.background
56
import androidx.compose.foundation.layout.Box
67
import androidx.compose.foundation.layout.fillMaxSize
78
import androidx.compose.foundation.layout.padding
8-
import androidx.compose.material.MaterialTheme
99
import androidx.compose.runtime.Composable
1010
import androidx.compose.ui.Modifier
1111
import androidx.compose.ui.layout.ContentScale
12+
import androidx.compose.ui.res.colorResource
1213
import androidx.compose.ui.res.painterResource
1314
import androidx.compose.ui.res.stringResource
15+
import androidx.compose.ui.tooling.preview.Devices
16+
import androidx.compose.ui.tooling.preview.Preview
1417
import androidx.compose.ui.unit.dp
15-
import org.wordpress.android.R.drawable
16-
import org.wordpress.android.R.string
18+
import org.wordpress.android.R
19+
import org.wordpress.android.ui.compose.theme.AppTheme
1720

1821
@Composable
1922
fun LoopingTextWithBackground(
2023
modifier: Modifier = Modifier,
2124
textModifier: Modifier = Modifier,
2225
) {
23-
Box(modifier.background(MaterialTheme.colors.background)) {
26+
Box(modifier.background(colorResource(R.color.bg_jetpack_login_splash))) {
2427
Image(
25-
painter = painterResource(drawable.bg_jetpack_login_splash),
26-
contentDescription = stringResource(string.login_prologue_revamped_content_description_bg),
28+
painter = painterResource(R.drawable.bg_jetpack_login_splash),
29+
contentDescription = stringResource(R.string.login_prologue_revamped_content_description_bg),
2730
contentScale = ContentScale.FillBounds,
2831
modifier = Modifier.matchParentSize(),
2932
)
@@ -35,3 +38,12 @@ fun LoopingTextWithBackground(
3538
)
3639
}
3740
}
41+
42+
@Preview(showBackground = true, device = Devices.PIXEL_4_XL)
43+
@Preview(showBackground = true, device = Devices.PIXEL_4_XL, uiMode = Configuration.UI_MODE_NIGHT_YES)
44+
@Composable
45+
fun PreviewLoopingTextWithBackground() {
46+
AppTheme {
47+
LoopingTextWithBackground()
48+
}
49+
}

WordPress/src/jetpack/java/org/wordpress/android/ui/accounts/login/components/PrimaryButton.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ import androidx.compose.foundation.layout.fillMaxWidth
44
import androidx.compose.foundation.layout.padding
55
import androidx.compose.material.Button
66
import androidx.compose.material.ButtonDefaults
7-
import androidx.compose.material.MaterialTheme
87
import androidx.compose.material.Text
98
import androidx.compose.runtime.Composable
109
import androidx.compose.ui.Modifier
11-
import androidx.compose.ui.graphics.Color
10+
import androidx.compose.ui.res.colorResource
1211
import androidx.compose.ui.res.stringResource
1312
import androidx.compose.ui.text.TextStyle
1413
import androidx.compose.ui.text.font.FontWeight
1514
import androidx.compose.ui.unit.dp
16-
import androidx.compose.ui.unit.sp
17-
import org.wordpress.android.R.string
15+
import org.wordpress.android.R
1816
import org.wordpress.android.ui.compose.unit.Margin
1917

2018
@Composable
@@ -24,20 +22,23 @@ fun PrimaryButton(
2422
) {
2523
Button(
2624
onClick = onClick,
25+
elevation = ButtonDefaults.elevation(
26+
defaultElevation = 0.dp,
27+
pressedElevation = 0.dp,
28+
),
2729
colors = ButtonDefaults.buttonColors(
28-
backgroundColor = MaterialTheme.colors.primary,
29-
contentColor = Color.White,
30+
backgroundColor = colorResource(R.color.bg_jetpack_login_splash_primary_button),
31+
contentColor = colorResource(R.color.text_color_jetpack_login_splash_primary_button),
3032
),
3133
modifier = modifier
3234
.padding(horizontal = 20.dp)
3335
.padding(top = Margin.ExtraLarge.value)
3436
.fillMaxWidth(),
3537
) {
3638
Text(
37-
text = stringResource(string.continue_with_wpcom_no_signup),
39+
text = stringResource(R.string.continue_with_wpcom_no_signup),
3840
style = TextStyle(
39-
fontWeight = FontWeight.Medium,
40-
letterSpacing = (-0.25).sp,
41+
fontWeight = FontWeight.SemiBold,
4142
),
4243
)
4344
}

0 commit comments

Comments
 (0)